오픈소스 공급망 공격 사건 하나가 5개월 만에 체포로 이어졌습니다. 호주연방경찰(AFP)은 8월 26일 웨스턴오스트레일리아 세 곳에서 압수수색을 집행하고, 다음 날 20대 남성 두 명을 퍼스 치안법원에 세웠습니다. 두 사람에게 적용된 혐의는 합쳐서 14건입니다. 이들이 속했다고 지목된 그룹이 올해 3월 Trivy와 Checkmarx KICS, LiteLLM을 연달아 오염시킨 TeamPCP입니다. 보안 스캐너가 공격 도구가 됐다는 점에서 당시에도 파장이 컸는데, 이번 기소로 피해 규모의 윤곽이 공식 수치로 나왔습니다.
공식 집계로 드러난 피해 규모
| 항목 | 수치 |
|---|---|
| 영향받은 조직 | 1,000곳 이상 (후속 분석에서는 최대 2,500곳) |
| 탈취된 자격증명 | 50만 건 이상 |
| 유출 데이터 | 최소 300GB |
| 복구 비용 | 전 세계 합산 수억 달러 규모로 추산 |
수사는 3월 침해 직후인 4월에 시작됐습니다. AFP는 웨스턴오스트레일리아 경찰, 미국 FBI와 병행 수사를 진행했습니다. FBI 사이버부문 부국장 브렛 리더먼은 이들의 악성 코드가 “전 세계 1,000곳이 넘는 조직을 잠재적으로 침해했다”고 밝혔습니다. AFP는 피의자들이 암호화폐로 대가를 받은 정황을 보고 있으며, 정확한 액수는 아직 조사 중입니다. 적용된 혐의에는 컴퓨터 범죄 목적의 데이터 소지와 공급, 무단 데이터 변경 외에 범죄수익 취급도 포함됐습니다.
나흘 만에 세 개 프로젝트
공격은 3월 19일부터 24일까지 네 차례 파도로 이어졌습니다. 시작은 잘못 설정된 GitHub Actions 워크플로 하나였습니다.
| 날짜 | 대상 | 수법 |
|---|---|---|
| 3월 19일 | trivy-action | Git 태그를 다시 써서 악성 릴리스 v0.69.4를 가리키게 변경 |
| 3월 20일 | Trivy | 워크플로 오설정으로 CI/CD 시크릿 탈취, 태그 삭제 후 악성 바이너리 강제 푸시 |
| 3월 23일 | Checkmarx KICS·AST | 1차에서 훔친 자격증명으로 GitHub Action 저장소 워크플로 변조 |
| 3월 24일 | LiteLLM | 탈취한 PyPI 배포 토큰으로 1.82.7·1.82.8을 직접 업로드 |
Trivy 침해에는 CVE-2026-33634가 부여됐고 CVSS 9.4로 평가됐습니다. 이후 파도는 Telnyx 파이썬 SDK, Bitwarden CLI, TanStack, Mistral AI로 번졌습니다. 배포 경로도 GitHub Actions, Docker Hub, npm, PyPI, OpenVSX로 갈렸습니다.
왜 하필 보안 스캐너였나
취약점 스캐너는 거의 모든 CI/CD 파이프라인의 초반에 실행됩니다. 그리고 그 시점의 러너에는 클라우드 자격증명, 레지스트리 토큰, 배포 키가 환경변수로 이미 올라와 있습니다. 스캐너를 오염시키면 별도의 권한 상승 없이 그 조직의 배포 권한을 그대로 손에 넣습니다.
실제로 LiteLLM 침해는 그렇게 일어났습니다. 공격자는 오염된 Trivy 스캐너가 LiteLLM의 CI에서 실행되는 순간 PYPI_PUBLISH 토큰을 빼냈고, 그 토큰으로 정상 경로를 통해 패키지를 올렸습니다. 서명과 해시 검증이 통과된 이유가 여기에 있습니다. 위조가 아니라 진짜 자격증명으로 배포된 진짜 릴리스였기 때문입니다.
LiteLLM이 여러 LLM 제공자의 API 키를 한곳에 모아두는 게이트웨이라는 점도 피해를 키웠습니다. 침해 한 번에 OpenAI, Anthropic을 비롯한 여러 서비스의 키가 한꺼번에 노출되는 구조였습니다.
.pth 파일이라는 발화 장치
두 악성 버전의 실행 방식이 서로 달랐다는 점은 눈여겨볼 만합니다. 1.82.7은 proxy_server.py에 base64로 인코딩한 페이로드를 심어뒀습니다. 해당 모듈을 import해야만 동작하는 방식입니다.
13분 뒤 올라온 1.82.8은 site-packages에 litellm_init.pth를 설치했습니다. 파이썬은 시작할 때 site-packages의 .pth 파일을 읽고, import로 시작하는 줄은 그대로 실행합니다. 패키지를 import하지 않아도, 심지어 pip나 IDE의 언어 서버가 파이썬을 띄우기만 해도 페이로드가 돕니다. 감염 조건에서 “그 라이브러리를 쓰는 코드”라는 전제가 사라진 셈입니다.
페이로드는 SSH 키, .env 파일, 셸 히스토리, AWS·GCP·Azure 자격증명, kubeconfig, 데이터베이스 접속 정보, 암호화폐 지갑까지 훑었습니다. 수집한 데이터는 AES-256-CBC로 암호화하고 세션 키를 4096비트 RSA 공개키로 감싼 뒤 tpcp.tar.gz로 묶어 공격자 도메인으로 전송했습니다. 그 도메인은 침해 하루 전인 3월 23일에 등록돼 있었습니다.
여기서 끝이 아니었습니다. ~/.config/sysmon/sysmon.py에 백도어를 설치하고 systemd 사용자 서비스로 등록해 5분마다 원격 명령을 받아왔습니다. 쿠버네티스 환경이면 kube-system의 모든 노드에 node-setup- 접두사를 단 특권 파드를 띄워 호스트 파일시스템을 마운트하고 같은 백도어를 심었습니다.
지금 확인할 것
악성 버전은 약 3시간 만에 격리됐지만, 그 사이 설치된 환경에는 백도어가 남아 있을 수 있습니다. PyPI에서 두 버전은 격리를 넘어 아예 삭제된 상태입니다.
$ python3 - <<'EOF'
import requests
d = requests.get('https://pypi.org/pypi/litellm/json').json()['releases']
for v in ('1.82.6', '1.82.7', '1.82.8', '1.83.0'):
print(v, '존재' if d.get(v) else '없음')
EOF
1.82.6 존재
1.82.7 없음
1.82.8 없음
1.83.0 존재
현재 버전 이력은 1.82.6에서 곧바로 1.83.0으로 건너뜁니다. 아래는 당시 환경을 되짚어볼 때 확인할 항목입니다.
# 설치된 버전 확인 (1.82.7 / 1.82.8이면 침해 대상)
pip show litellm | grep -i version
# 지속성 흔적
ls -la ~/.config/sysmon/sysmon.py
systemctl --user status sysmon.service
ls /tmp/tpcp.tar.gz /tmp/session.key 2>/dev/null
# 쿠버네티스 특권 파드
kubectl get pods -A | grep node-setup-흔적이 하나라도 나오면 파일 삭제로 끝낼 일이 아닙니다. 유출된 자격증명은 이미 공격자 손에 있다고 봐야 하므로 SSH 키, 클라우드 키, API 키, kubeconfig, 릴리스 서명 키를 전부 교체하고 깨끗한 환경에서 재설치하는 것이 정석입니다. FBI도 이번 발표에서 유출 데이터와 자격증명을 “지속적 위험”으로 다루라고 권고했습니다.
체포로 끝나지 않는 이유
이 그룹이 사용한 자가 확산 웜 프레임워크 Mini Shai-Hulud는 5월 12일 GitHub에 오픈소스로 공개됐습니다. 그리고 8월 npm의 keyv, cacheable 패키지 공격에 그대로 재사용됐습니다. 도구가 공개된 이상 원저작자가 구속되더라도 같은 수법은 계속 반복됩니다.
이번 사건이 남긴 교훈은 서명과 해시 검증만으로는 부족하다는 것입니다. 정상 자격증명으로 배포된 악성 패키지는 모든 검증을 통과합니다. 결국 버전 고정, 릴리스 시점과 배포 주체를 함께 보는 모니터링, 파이프라인 시크릿의 최소 권한 설계 같은 기본기가 남습니다. 스캐너를 돌리는 러너가 무엇을 볼 수 있는지 한 번쯤 점검해볼 시점입니다.
참고
- Help Net Security — Two alleged TeamPCP hackers arrested over global supply chain attacks
- The Hacker News — Alleged TeamPCP Hackers Charged in Australia
- Arctic Wolf — TeamPCP Supply Chain Attack Campaign
- Snyk — How a Poisoned Security Scanner Became the Key to Backdooring LiteLLM
- Microsoft Security — Trivy 공급망 침해 탐지·조사 가이드