LiteLLM 7개월 만에 또 악성 배포…열쇠는 자사 로그에 남은 GitHub 토큰
주간 2,300만 번 내려받는 LiteLLM의 1.107.0이 10월 21일 PyPI와 Docker latest 태그로 2시간 넘게 배포됐습니다. SSH 키, 클라우드 자격증명, 공급자 API 키를 빼가는 코드였고 안전 버전은 1.106.2입니다.
- LiteLLM
1.107.0이 PyPI와 Dockerlatest로 약 2시간 배포됐습니다. - 설치만 해도 클라우드 자격증명과 공급자 API 키를 빼가는 코드였습니다.
- 공격자는 사내 LiteLLM 로그에서 GitHub 토큰을 얻은 것으로 회사는 봅니다.
AI 모델 여러 개를 API 하나로 묶어 주는 오픈소스 게이트웨이 LiteLLM의 공식 배포판에 자격증명 탈취 코드가 실려 나간 사실이 10월 21일 확인됐습니다. 감염된 버전은 litellm==1.107.0이고, PyPI에는 11시 39분부터 13시 23분(UTC)까지 올라가 있었습니다. Docker Hub와 GHCR의 latest 태그도 같은 시간대에 악성 이미지를 가리켰습니다. LiteLLM은 PyPI에서 주간 약 2,300만 번 내려받는 패키지입니다(Socket 집계).
LiteLLM이 공급망 공격을 당한 것은 올해 3월 이후 두 번째입니다. 3월 TeamPCP 사건 뒤 배포 체계를 크게 바꿨는데도 다시 뚫렸습니다. 회사가 밝힌 경로가 의외입니다. 공격자는 엔지니어 세 명의 GitHub 토큰을 썼고, LiteLLM은 그 토큰이 자사가 내부용으로 돌리던 LiteLLM 서버의 로그에서 나갔다고 봅니다.
설치만 해도 돈다
악성 코드는 패키지 안에 파일 세 개로 들어 있었습니다. cook.pth, cook.py, sitecustomize.py입니다. 이 중 cook.pth와 sitecustomize.py는 파이썬이 시작할 때마다 자동으로 읽는 파일이라, 코드에서 import litellm을 하지 않아도 실행됩니다. SafeDep 분석에 따르면 pip 명령, IDE 언어 서버, CI 작업처럼 그 환경에서 파이썬이 한 번이라도 뜨면 돌아갑니다.

하는 일은 두 가지였습니다.
- 자격증명 수집(
cook.py): SSH 키, AWS·Azure·GCP·Kubernetes 자격증명, CI 비밀값, 브라우저 저장 비밀번호, 셸 기록을 모읍니다. Claude Code·Cursor·Copilot·Codex의 인증 파일도 대상입니다. LiteLLM 프록시를 돌리는 서버라면 DB의LiteLLM_CredentialsTable까지 읽는데, 여기에 OpenAI·Anthropic 같은 공급자 API 키가 모여 있습니다. 모은 것은 암호화해www.litellm[.]dev라는 가짜 도메인으로 보냅니다. - 백도어(
sitecustomize.py): 부모 프로세스에서 떨어져 나온 데몬으로 남아 1시간마다 공격자의 GitHub 저장소에서 서버 주소를 읽고, 그 주소에서 받은 파이썬 코드를 실행합니다. 이 저장소는 13시 40분쯤 삭제돼 지금은 할 일이 없지만, 그 전에 2차 코드를 받아 간 호스트가 있었는지는 확인되지 않았습니다.
훔친 자격증명은 실제로 쓰였습니다. Wiz는 유출 약 4시간 30분 뒤인 16시 20분부터 두 IP에서 탈취된 AWS 인스턴스 역할로 sts:GetCallerIdentity와 bedrock:ListFoundationModels를 호출한 기록을 관찰했습니다. 남의 클라우드 계정으로 쓸 수 있는 AI 모델 목록부터 확인한 것으로, 피해자 돈으로 모델을 돌리는 LLM재킹에서 흔히 보이는 첫 단계입니다.
3월 뒤에 잠근 문은 어떻게 열렸나
3월 사고 뒤 LiteLLM은 배포 절차를 여러 겹으로 잠갔습니다. 회사 사고 보고에 적힌 내용입니다.
- 배포 작업을 비공개 저장소로 옮겼습니다.
- PyPI 업로드는 Trusted Publishers(GitHub 워크플로만 올릴 수 있게 하는 PyPI 기능)로만 합니다.
- Docker 태그를 덮어쓸 수 없게 하고 이미지에 서명합니다.
- 최종 배포는 사무실에 늘 놓여 있는 노트북 한 대에 묶인 계정만 할 수 있습니다.
공격자는 이번에 이 단계를 하나씩 우회했습니다. 사고 보고와 Socket·Wiz 분석을 합친 순서는 아래와 같습니다.
BerriAI/litellm 대신 하이픈 하나 다른 Berri-AI/litellm에서 빌드 시작배포 버튼은 여전히 그 노트북에 묶인 정식 워크플로가 눌렀습니다. 공격자는 워크플로가 어느 저장소의 코드를 빌드할지만 바꿨습니다. 그래서 결과물에는 정상 배포와 같은 서명이 붙었고, GitHub의 공식 v1.107.0 태그에는 악성 파일이 없습니다. 소스와 패키지를 대조하지 않으면 정상 릴리스와 구별할 수 없었습니다.
토큰은 LiteLLM 로그에 있었다
LiteLLM의 설명에서 가장 낯선 대목은 토큰 출처입니다. 회사는 내부용으로 운영하던 LiteLLM 서버에서 이름만 그럴듯한 정체불명 사용자 계정을 찾았고, 이 서버는 공개 인터넷에 열려 있었습니다. 공격자가 이 계정으로 서버 로그를 읽어 엔지니어들의 GitHub CLI 토큰을 가져갔다는 것이 회사 판단입니다.
그 토큰이 왜 로그에 있었는지도 밝혔습니다. 사고 보고에 따르면 LiteLLM은 "특정 상황에서 Claude Code가 사용자의 GitHub CLI OAuth 토큰을 응답 본문에 넣는 것"을 관찰했습니다. 엔지니어들이 Claude Code 요청을 사내 LiteLLM 게이트웨이로 보내고 있었고, 게이트웨이가 응답 본문을 기록하도록 설정돼 있으면 그 토큰이 로그에 그대로 남는다는 설명입니다.
아직 확인되지 않은 부분도 있습니다.
- 공격자가 그 사내 서버에 처음 어떻게 들어왔는지는 확인되지 않았습니다. 회사는 "최근 패치된 취약점"을 의심한다고만 적었습니다.
- LiteLLM은 기업 고객용 비공개 레지스트리에 10월 27일 공개 예정인 취약점을 고친 엠바고 버전을 배포하고 있다고 같은 글에서 밝혔습니다. 두 취약점이 같은 것인지는 말하지 않았습니다.
- Claude Code가 어떤 조건에서 토큰을 응답에 넣는지, Anthropic의 입장은 무엇인지는 10월 22일 기준 공개된 내용이 없습니다.
같은 주에 AI 공급자 키를 맡아 두는 서비스의 침해가 잇따랐습니다. 10월 17일 OpenCode Console에서 사용자 API 키가 노출됐고, 10월 21일 배포 플랫폼 Railway도 고객 환경변수 저장소 침해를 공개했습니다. Railway는 이를 "AI 추론 공급자를 노린, 플랫폼 기업 대상 연쇄 공격의 일부"로 본다고 했습니다. LiteLLM 사건과 같은 공격자인지는 어느 회사도 밝히지 않았습니다.
내 환경이 해당되나
LiteLLM 공식 사고 보고가 정한 영향 범위는 아래와 같습니다. 지역과 무관하게 전 세계 PyPI·Docker 사용자가 같은 조건입니다. 한국 시간으로는 21일 저녁 8시 39분부터 22일 0시 23분까지가 PyPI 노출 구간입니다.
| 설치 경로 | 해당 조건 | 노출 시간(UTC) |
|---|---|---|
PyPI (pip install litellm) | 버전을 고정하지 않고 설치해 1.107.0을 받은 경우 | 11:39~13:23 |
| Docker Hub | v1.107.0, latest, main-latest, main-stable 이미지를 받아 실행 | 11:46~14:20 |
GHCR (litellm, litellm-database, litellm-non_root) | 위와 같은 태그 | 11:46~13:51 |
| 기업용 비공개 레지스트리 | 해당 없음 | 없음 |
안전한 버전은 1.106.2와 그 이전에 명시적으로 고정한 버전입니다. LiteLLM은 해당 시간대에 1.107.0을 설치했거나 해당 이미지를 띄웠다면 그 환경을 침해된 것으로 간주하라고 권합니다. /tmp/.pc_once_ 표식 파일이 있으면 유출이 확정이지만, 없다고 안전한 것은 아닙니다. 컨테이너가 재시작되면 지워지기 때문입니다.
사고 보고와 발견자 LidorMachluf의 지침을 합친 정리 순서는 다음과 같습니다. pip install부터 실행하면 안 됩니다. pip도 파이썬이라 악성 파일이 다시 돕니다.
- 셸에서 모든
site-packages의cook.pth,cook.py,sitecustomize.py와 해당__pycache__파일을 지웁니다. /tmp/remote_code_loader.lock을 잡고 있는 파이썬 데몬을 종료합니다.litellm==1.106.2로 고정해 다시 설치합니다. 가상환경과 이미지를 새로 만드는 쪽이 낫습니다.- 그 호스트가 닿던 비밀값을 모두 교체합니다. 인스턴스 IAM 역할, 프록시 DB에 저장된 공급자 키 전부와 마스터·가상 키,
DATABASE_URL비밀번호가 포함됩니다.
LiteLLM 프록시를 Docker latest 태그로 띄우고 자동 재시작이나 이미지 갱신을 걸어 둔 팀이라면, 실행 중인 컨테이너의 이미지 다이제스트부터 확인해 보세요. sha256:1bbcb1443bc9로 시작하거나 21일 11시 46분부터 14시 20분(UTC) 사이에 재시작된 기록이 있으면, 위 순서대로 정리하고 프록시 DB에 넣어 둔 공급자 키를 가장 먼저 교체하면 됩니다.