서론
2026년 10월 21일, AI 모델을 호출하는 Python 라이브러리 LiteLLM의 1.107.0 버전이 키를 도난당하는 악성 코드가 포함된 가짜 패키지로 대체되었습니다. 이는 PyPI와 Docker 이미지 모두에서 발생했습니다.
이 사건에서 가장 이해하기 어려운 부분은, LiteLLM을 import 하지 않아도 Python을 실행하는 것만으로 작동한다는 점입니다. 게다가, 새 버전으로 업데이트하는 것만으로는 안전하지 않으며, 키를 교체해야 합니다.
본 기사에서는 다음 내용을 다룹니다:
- 무엇이 발생했는지
- 어떻게 공개 절차가 탈취되었는지
- 왜 import 하지 않아도 작동하는지 (
.pth 파일의 메커니즘)
- 무엇이 도난당했으며, 어떻게 확인하고, 만약 설치했다면 무엇을 해야 하는지
이를 그림으로 정리했습니다.
같은 내용을 영상(3분)으로도 설명했습니다. 1분에 요약한 짧은 버전도 있습니다.
무엇이 발생했는가
LiteLLM은 OpenAI, Claude, Gemini 등 다양한 AI 모델을 동일한 방식으로 호출할 수 있게 해주는 Python 라이브러리(및 프록시)입니다. AI를 활용하는 애플리케이션의 기반으로 널리 사용됩니다.
그 1.107.0 버전이 공격자가 준비한 가짜 패키지로 공개되었습니다.
LiteLLM の役割と、偽物が公開されていた時間
| 대상 | 공개되었던 시간 (UTC) | 한국 시간 |
|---|
PyPI의 litellm==1.107.0 (7가지 wheel) | 10/21 11:39~13:23 (약 2시간) | 20:39~22:23 |
Docker 이미지 (v1.107.0 ・latest ・main-latest ・main-stable 등) | 10/21 11:46~14:20 (약 2시간 반) | 20:46~23:20 |
- 그 이전 버전 (
1.106.2 등)은 안전한 것으로 간주됩니다 - GitHub의 v1.107.0 태그(소스 코드)에는 악성 파일이 포함되어 있지 않습니다. 가짜는 다른 리포지토리에서 만들어졌습니다 - LiteLLM은 올해 3월에도 또 다른 탈취를 당했으며, 이번이 두 번째입니다.
어떻게 침투했는가
LiteLLM의 근본 원인 분석에 따르면, 침입 경로는 5개월 전의 '실수'였습니다.
攻撃の流れ: 5/27 に鍵を公開 → 10/10 管理者のトークン → 別の 2 人のトークン → 10/21 公開
| 일시 (UTC) | 사건 |
|---|
| 2026/5/27 | 사내 Agent Platform 관리용 키(마스터키)가 포함된 설정 파일을 공개 리포지토리에 커밋. 즉시 덮어썼지만(force push), 키 자체는 무효화하지 않았음 |
| 10/10 21:27 | 공격자가 해당 키로 Agent Platform의 API를 호출하여 GitHub 조직 관리자(Owner)의 GitHub CLI 토큰을 입수 |
| 10/10 22:11~ | 관리자의 권한으로 임시 브랜치에 CI (GitHub Actions)를 추가. CI의 비밀 값을 외부로 전송하게 하여, 다른 두 명의 유지보수 담당자(maintainer)의 토큰을 탈취함. 실행 기록은 즉시 삭제 |
| 10/21 10:08~ | 공개 절차를 수정하여 매우 유사한 가짜 리포지토리에서 만들도록 실행 |
| ... | |
| 偽のリポジトリから作って、盗んだトークンで承認して公開 | |
가짜 리포지토리 이름은 Berri-AI/litellm이며, 실제 BerriAI/litellm과는 하이픈 하나만 다릅니다.
또한, Agent Platform이 사용하던 GitHub 토큰은 서비스용 계정이 아니라 관리자 개인의 것이었습니다. 권한도 제한되어 있지 않았습니다. 게다가, 평소에는 특정 단말기에서만 승인할 수 있는 공개 절차가 그 주 동안만 '엔지니어 2명이라면 누구나'로 느슨하게 설정되어 있었습니다. 작은 방심들이 여러 개 중첩되었습니다.
왜 import 하지 않아도 작동하는가
이 부분이 이번의 가장 중요한 포인트입니다.
가짜 1.107.0 wheel은 litellm 폴더와 별개로, site-packages 최상단에 다음 3개의 파일을 배치합니다.
site-packages/
├── litellm/
├── cook.pth ← 내용은 「import cook」의 1줄뿐
...
import しなくても動く理由: 起動時に .pth の import 行が実行される
Python은 시작할 때, 표준 site 모듈이 site-packages 안의 .pth 파일을 읽습니다. .pth
원래 이 파일은 '이 폴더도 import 대상에 추가'라는 경로를 작성하기 위한 파일입니다. 하지만, import로 시작하는 줄은 그대로 실행됩니다.
즉,
python을 실행할 때 (스크립트에서든, 대화형 모드에서든, 테스트에서든) -
site가 cook.pth를 읽고 import cook을 실행하며 -
cook.py가 작동하는
이러한 흐름으로, LiteLLM을 한 번도 import 하지 않아도 악성 코드가 작동합니다.
pip 역시 Python으로 작동하기 때문에, pip install이나 pip uninstall를 실행하는 것만으로도 다시 작동할 수 있습니다. LiteLLM 공식 대응 절차에서 '먼저 pip을 실행하지 말라'고 안내하는 이유가 바로 이것입니다.
sitecustomize.py 역시 Python 시작 시 자동으로 로드되는 특별한 이름의 파일입니다. 이 파일은 나중에 설명할 '1시간 간격 명령'을 작동시킵니다.
무엇이 도난당했는가
cook.py는 사용자의 주변에 있는 키나 토큰들을 모조리 수집합니다.
盗まれる対象と送り先
- AWS, Google Cloud, Azure 등 클라우드 키 (인스턴스의 메타데이터를 통해서도 획득)
- SSH 또는 GPG 키, 각종 비밀키
- GitHub, npm, PyPI 등의 토큰
- Chrome 계열이나 Firefox의 비밀번호/쿠키
.env 파일, 실행 중인 프로세스의 환경 변수, 쉘 히스토리 - Kubernetes, Vault, CI의 토큰
Claude Code, Cursor, Copilot, Gemini CLI, Codex 등 AI 코딩 도구의 인증 정보 및 설정 - LiteLLM 프록시 데이터베이스에 있는 각 AI 서비스의 API 키
수집된 정보는 암호화하여 모은 후 www.litellm.dev/healthcheck로 전송됩니다. 실제 도메인은 litellm.ai이므로, 이름만 진짜처럼 보이게 만든 것입니다.
1시간마다 '다음 명령'을 가져오기
sitecustomize.py는 백그라운드에서 계속 작동하는 프로세스를 만들고, 1시간마다 다음 과정을 반복합니다.
1 時間ごとに domain.txt を読み、書かれたサーバーからプログラムを取ってきて実行する
- GitHub 상의
domain.txt라는 파일을 읽는다 - 거기에 적힌 서버로부터 /codepy를 가져온다 - 그대로 실행한다
조사 당시에는 domain.txt가 비어 있었고, 이 장치는 사용되지 않았습니다. 하지만 파일에 단 한 줄을 작성하는 것만으로도 감염된 모든 환경에서 원하는 프로그램을 작동시킬 수 있는 상태가 되었습니다.
StepSecurity의 분석에 따르면, 해당 계정과 리포지토리는 이미 삭제되었습니다. 다만 감염된 환경은 동일한 URL로 계속 접근하기 때문에, 누군가가 같은 사용자 이름을 다시 가져와 파일을 배치한다면 여전히 코드를 작동시킬 수 있다고 지적됩니다.
업데이트만으로는 부족한 이유
更新しても仕掛けは残り、盗まれた鍵はもう相手の手元にある
- 장치는 남아있다:
pip install -U litellm으로 litellm을 새로 설치하더라도, site-packages의 가장 상단에 위치한 cook.pth, cook.py, sitecustomize.py는 사라지지 않습니다.
- 키는 이미 전송되었다: Python을 단 한 번이라도 실행했다면, 그 시점에서 키는 이미 전송된 것입니다. 외부 네트워크가 연결되는 환경이라면, LiteLLM은 '전송되었다는 가정 하에 작동하도록' 호출하고 있습니다.
확인 방법
Python (pip 포함)을 실행하지 않고 파일을 찾습니다.
# cook.pth나 cook.py가 놓여있지 않은지
find / -name "cook.pth" -path "*site-packages*" 2>/dev/null
find / -name "cook.py" -path "*site-packages*" 2>/dev/null
...
아울러, requirements.txt나 잠금 파일인 pyproject.toml에서 litellm이 1.107.0으로 되어 있지 않은지, 또는 litellm>=처럼 상한선 없이 지정되어 있지는 않은지, Docker의 태그를 latest 등으로 추적하고 있지는 않은지도 확인해야 합니다.
이미 설치했다면
LiteLLM의 공식 절차를 순서대로 정리합니다.
入れてしまったときの 4 つの手順
작동하는 장치를 멈추기 (pip보다 먼저):
process가 /tmp/remote_code_loader.lock 파일을 가지고 있다면 해당 프로세스를 중지시킵니다.
파일 삭제: 영향을 받은 모든 site-packages에서 다음 파일들을 삭제합니다: cook.pth, cook.py, sitecustomize.py 및 대응하는 __pycache__ 폴더의 파일들.
환경 재구축: 수정하는 것보다 처음부터 다시 만드는 것이 확실합니다. 가상 환경이나 컨테이너를 처음부터 구축하고, litellm==1.106.2와 같이 공개 전 버전으로 고정합니다. Docker는 다이제스트(digest)로 고정하며, 캐시나 미러에 남아있는 1.107.0도 삭제해야 합니다.
접근 가능한 모든 키 교체: 클라우드의 키, IAM Role, SSH/GPG 키, 브라우저의 비밀번호와 세션, GitHub/npm/PyPI 토큰, Kubernetes/Vault/CI의 비밀 값, AI 도구의 토큰 등입니다. LiteLLM 프록시를 사용했다면 데이터베이스에 저장된 각 AI 서비스의 API 키나 마스터 키도 대상이 됩니다.
AWS를 사용하는 경우, CloudTrail을 통해 설치 시점 근처의 GetCallerIdentity 호출 기록과 평소와 다른 곳에서의 이용 여부를 확인합니다.
자신의 프로젝트에서 할 수 있는 예방
이번 가짜 패키지는 약 2시간 만에 사라졌지만, 그 사이에 pip install litellm 또는 latest 이미지를 가져온 환경은 감염되었습니다.
버전 고정: 상한선이 없는 지정(예: litellm>=1.100)이나 latest 태그는 공개되는 순간의 가짜 패키지를 그대로 가져옵니다.
해시값으로 고정하기: uv.lock 또는 pip install --require-hashes와 같이 파일의 해시값까지 고정하면, 같은 버전 번호라도 내용물이 다른 것은 설치되지 않습니다.
Docker 이미지 지정 시 다이제스트 사용: 태그는 나중에 변경될 수 있지만, 다이제스트(@sha256:...)는 그 내용물 자체를 가리킵니다.
막 공개된 버전을 즉시 사용하지 않기: uv의 exclude-newer처럼 특정 날짜보다 새로운 버전은 사용하지 않도록 설정하는 것도 있습니다. 이번 사례처럼 몇 시간 만에 사라지는 가짜 패키지는 조금 기다리는 것만으로도 피할 수 있습니다.
팁: 유출된 키는 '삭제'보다 '무효화'
이번 침입 경로는 공개 리포지토리에 실수로 커밋한 키였습니다. 커밋은 즉시 덮어쓰기(force push)되었지만, 키 자체는 무효화되지 않았습니다.
履歴から消しても、写した人の手元では鍵は生きている
- Git 히스토리에서 삭제하더라도, 그보다 먼저 누군가 리포지토리를 가져갔다면 키는 그 사람의 손에 남아있을 수 있습니다.
- 공개 리포지토리는 키를 찾는 프로그램에게 항상 감시당하고 있다고 생각하는 것이 안전합니다.
무효화해 버리면, 복사되어 있어도 사용할 수 없습니다.
유출된 키는 먼저 무효화(또는 교체)해야 합니다. 히스토리에서 삭제하는 것은 그 이후에 해야 합니다.
요약
- LiteLLM의
1.107.0 (PyPI와 Docker)이 2026년 10월 21일에 키를 도난당하는 가짜 패키지로 대체되었습니다. 침입 경로는 5월에 실수로 공개하고 무효화하지 않은 키였습니다. 이를 통해 관리자 토큰, 다른 두 사람의 토큰, 그리고 공개 절차 순서대로 탈취되었습니다.
- Python은 시작 시
.pth 파일의 import 구문을 실행하므로, import 하지 않아도 pip를 실행하는 것만으로 동작할 수 있습니다. 업데이트만으로는 충분하지 않습니다. 장치를 멈추고 삭제하며 환경을 재구축하고,
모든 키를 교체해야 합니다. 예방은 해시값 고정, 다이제스트 지정, 막 공개된 버전을 즉시 사용하지 않는 것입니다.
- 유출된 키는 삭제보다 무효화가 우선입니다.
의존하는 라이브러리에 대해서는 평소에 크게 신경 쓰지 않지만, '설치하는 순간 자신의 권한으로 무엇이든 할 수 있게 되는' 것이라는 점을 다시 한번 느꼈습니다.
출처
- Contained: 2026년 10월 21일 LiteLLM 릴리스 v1.107.0 탈취 (LiteLLM 공식)
- LiteLLM v1.107.0 공격 흐름 (LiteLLM 공식, 10월 23일 근본 원인 분석)
- litellm 1.107.0 PyPI 탈취 (StepSecurity)
- LiteLLM 1.107.0 공급망(supply chain) 탈취 내부 분석 (Microsoft Security Blog)
- 새로운 증거, LiteLLM이 공급망 공격 11일 전에 비밀 정보(Secret-Stealing) 워크플로우를 실행했음을 보여주다 (Socket)
- LiteLLM, 올해 두 번째 공급망 공격을 받다 (Help Net Security)
- 사이트별 설정 후크(Site-specific configuration hook) (Python 문서)
논의