🔥 一个故事
2026年3月24日,一个普通的星期一。
如果你在生产环境跑着 LiteLLM——那个帮你连接 OpenAI、Anthropic、AWS Bedrock 的 Python 库——你可能正在经历这辈子最漫长的 3 小时。
有人偷偷在你的服务器上放了个"后门",偷走了:
而这一切,只用了 3 小时。
340 万人每天下载它的 Python 包——这次攻击的影响力,堪比特斯拉刹车门。
🎪 这场攻击是怎么发生的?让我从头说起
你以为故事从 LiteLLM 开始?错了。
故事的起点,是一个叫 Trivy 的安全扫描工具。
第一幕:Trivy 的"家门"被撬开了
Trivy 的 GitHub Action 有一个漏洞——pull_request_target 工作流问题。攻击者利用它,偷走了维护者的凭证。
这就像:
你家大门锁得很好,但楼道的窗户没关。小偷从窗户进来,偷走了你放在鞋柜里的钥匙
第二幕:多米诺骨牌倒下
有了凭证,攻击者(一个叫 TeamPCP 的组织)做了两件事:
- 把 Trivy v0.69.4 的发布标签偷偷换成恶意版本
- 用同样手法攻陷了 Checkmarx KICS GitHub Action
然后,LiteLLM 的 CI/CD 流水线——没有锁定版本——拉取了那个被污染的 Trivy Action。
第三幕:致命一刀
恶意 Action 从 GitHub Actions 运行器中,偷走了 LiteLLM 的 PyPI 发布令牌。
然后?
TeamPCP 以"合法"身份,向 PyPI 发布了两个后门版本:1.82.7 和 1.82.8。
这就像有人偷了你的身份证,去银行开了你的账户——一切看起来完全合法。
🕵️ 这个恶意软件到底做了什么?
让我告诉你一个三阶段怪兽的故事:
第一阶段:信息收集
这个恶意软件就像一个无孔不入的"数字小偷",它会偷:
- 你的钥匙:SSH 私钥、.env 文件、Git 凭证
- 你的密码本:Slack、Discord webhook、Jenkins、Travis CI 配置
- 你的保险箱:/etc/shadow 密码哈希、AWS 完整凭证链
- 你的云帝国:GCP、Azure 服务账户、Docker、Kubernetes 秘密
- 你的钱包:比特币、以太坊、索拉纳、卡尔达诺、门罗币...
它不是随便偷——它是"能拿多少拿多少"。
第二阶段:打包运走
偷来的东西,用 AES-256-CBC 加密——相当于放进保险箱,然后用 4096 位 RSA 公钥锁死。
运到哪里?
models.litellm.cloud——这个域名,攻击前 1 天才注册。
就像小偷说:"我把东西藏到新租的仓库,谁也查不到。"
第三阶段:赖着不走
这才是最可怕的:
- 在你的 ~/.config/sysmon/sysmon.py 留下后门
- 每 5 分钟去 checkmarx.zone"领任务"
- 在 Kubernetes 里,它能读取所有命名空间的秘密
- 还在每个 kube-system 节点部署特权 Pod,挂载你的主机文件系统
这不只是一次偷窃——这是一场"鸠占鹊巢"。
💡 两个"诡异"的投递方式
我必须重点讲讲这两个版本,因为它们太阴了:
版本 1.82.7:藏在代码里的定时炸弹
把恶意代码直接嵌在 litellm/proxy/proxy_server.py 里。
每次你 import litellm.proxy,它就悄悄执行。
就像你在家里藏了个特工——只有你叫他的名字,他才会出来。
版本 1.82.8:.pth 文件的暗黑魔法
这是最恐怖的部分。
它放了一个 litellm_init.pth 文件到 site-packages/。
你知道这意味着什么吗?
Python 的 .pth 文件机制——每次解释器启动都会执行。
- 你运行
python -c "print('hello')"?执行一次
它甚至不需要你导入 litellm——它就在那里,等着你启动 Python。
这就是 MITRE ATT&CK 里的 T1546.018——Python 启动钩子。
最阴的是:这个 .pth 文件在 wheel 的 RECORD 里被正确声明,所以 pip install --require-hashes 也检测不出来。
🏭 谁被炸到了?
| |
|---|
| |
| |
| |
| |
| |
| Aider | ✅ 安全(固定版本 litellm==1.82.3) |
Aider 活下来的原因很简单:它固定了依赖版本。
这就像:
别人都在吃外卖,只有你自己带饭——结果外卖被下毒了。
🛡️ 为什么标准防御全失效了?
这是一个细思极恐的问题:
- ✅ 拼写检查:通过(包名就是 litellm,没毛病)
- ✅ 域名检查:通过(models.litellm.cloud 看起来像官方的)
- ✅ pip install --require-hashes:通过(.pth 文件被正确声明)
唯一能发现问题的,是检查是否安装了包含 subprocess/base64/exec 的 .pth 文件——但这个检查,目前没有广泛部署的工具能自动做。
🎭 攻击者的"骚操作"
当研究员 Callum McMahon 在 GitHub 上报告这个漏洞时,发生了什么?
TeamPCP 做了件让人毛骨悚然的事:
- 然后用被黑掉的维护者账户 krrishdholakia 把 issue 关了,理由是"未计划"
"这感觉就像:小偷不仅潜入你家,还删掉了你的监控录像。"
🤔 没人敢说的结构性问题
让我说点得罪人的话。
LiteLLM 是 Python 包,它站在你的应用和 LLM 提供商之间——它持有你所有的 API 密钥。从设计上,它就是一个高价值目标。
但 Python 的特性让它更危险:
- 📦 .pth 文件:任何包都能在解释器启动时执行代码
- 🔗 传递依赖:你装一个包,它会偷偷带来几十个你从未听过的包
- 🔑 CI/CD 令牌:一个被盗的令牌,就能用"官方"名义发布假包
- 🏃 GIL:你需要跑多个进程,每个进程都会触发 .pth 执行
这并不是说 Python 不好——Python 在它擅长的领域很棒。
但问题是:Python 是承载你最高敏感凭证、处于每个 LLM 请求关键路径上的最佳选择吗?
对比一下 Go 和 Rust:
攻击面从根本上就小很多。
⚡ 你现在该做什么?
1️⃣ 检查你的 LiteLLM 版本
pip show litellm | grep Version
看到 1.82.7 或 1.82.8?你可能已经中招。
2️⃣ 扫描 .pth 文件
find $(python -c "import site; print(site.getsitepackages()[0])") -name "*.pth" -exec grep -l "subprocess\|base64\|exec" {} \;
3️⃣ 旋转所有凭证
如果你跑过被攻击的版本,假设所有凭证都已泄露:
- API 密钥(OpenAI、Anthropic...)
4️⃣ 固定你的依赖项
像 Aider 学习:
litellm==1.82.6 --hash=sha256:<known-good-hash>
5️⃣ 锁定 CI/CD Action
❌ 别这样做:
uses:aquasecurity/trivy-action@latest
✅ 要这样做:
uses:aquasecurity/trivy-action@<specific-commit-sha>
💎 故事的最后
这次攻击撕开了 Python 生态的"皇帝新衣":
Aider 活下来了,因为它做了最简单的事:固定版本。
有时候,最土的办法,反而是最有效的。
🎓 QPython 官方课程推荐
根据本文讨论的主题,如果你想深入学习:
| |
|---|
| 安全 / 供应链攻击 | |
| Python 生态与包管理 | |
| AI / LLM 应用 | |
学完课程后再回来读这篇文章——你会理解我为什么这样写。
"技术不是黑盒子,它有自己的故事和逻辑。了解故事,才能更好地使用技术。"