← 所有文章

Fork 炸弹救了我们

LiteLLM 1.82.8 中的恶意代码藏在一个 .pth 文件里,任何 Python 启动时它都会运行。它会收集 SSH 密钥、云凭据、加密货币钱包和 CI/CD 密钥,用 4096 位 RSA 密钥加密后,把打包好的数据外传到攻击者控制的域名。载荷做得很扎实,加密没有破绽,外传过程干净利落。1 这起事件属于我的代理安全系列——关注那些真实发生的失败,以及它们如何塑造我们对自动化系统的信任方式。5

问题出在,这个 .pth 文件会派生一个 Python 子进程去干活。而子进程启动时,又会触发 .pth 文件。于是再派生一个子进程,再触发一次。指数级的 fork 炸弹在几秒钟内吃满 100% CPU 和 5 GB 以上内存。2

fork 炸弹是个 bug。攻击者并不希望恶意代码暴露。如果实现正确,它本可以在每一台被感染的机器上、每一次 Python 调用时悄无声息地运行,可能持续数周。结果开发者发现自己的机器卡到几乎瘫痪,一查,揪出了这个凭据窃取程序。发布后 46 分钟,PyPI 隔离了这两个版本。1

四万六千次安装,四十六分钟。所谓的检测机制,其实是恶意代码里的一处实现错误。

当 AI 代理的某个依赖装进了含有 fork 炸弹的软件包时,指数级的进程派生会在几秒内占满 100% CPU 和数 GB 内存,机器随即无法使用。 在 LiteLLM 1.82.8 这起事件中,这个意外的 fork 炸弹是凭据窃取载荷被发现的唯一原因:开发者注意到机器卡死,才去追查。若没有这个 bug,恶意代码会悄悄外传 SSH 密钥、云凭据和加密货币钱包,数周之后才可能被发现。

摘要

  • 那个 bug:LiteLLM 1.82.8 的凭据窃取程序带着一个 fork 炸弹 bug,把被感染的机器拖垮。没有这个 bug,它会安静地跑上好几周。
  • 那个缺口:静态分析、行为监控、代码审查,全都没能拦下这次攻击。每一层检测都指望另一层会兜住,结果谁也没兜住。3
  • 那条曲线:攻击者的水平会随着迭代提升。.pth 技术如今已被公开记录在案。下一个攻击者继承的是不带 bug 的版本。
  • 不靠运气也管用的做法:出站流量的域名年龄检查、软件包安装的行为基线、文件系统诱饵文件(canary)、安装环境隔离。这几项都与载荷质量无关,照样有效。
  • 不对称之处:环境由防守方来定。如果安装环境里根本没有凭据可偷,再完美的载荷也一无所获。

我们只是运气好

把 fork 炸弹从载荷里去掉,这次攻击就会在无声无息中得手。.pth 文件的执行早于任何 import,早于任何应用代码,也早于任何 Python 层面的沙箱。没有钩子挂载点,没有日志记录。凭据窃取程序跑完、加密、外传,Python 进程一切照旧。开发者什么都看不见。CI 流水线什么都看不见。安全扫描工具也什么都看不见——因为安全扫描工具本身就是这次的攻击入口。3

LiteLLM 1.82.8 的发现过程,不是“我们的监控抓到了它”,而是“攻击者写出了一个 bug”。

把供应链安全建立在这样的基础上,实在让人不安。正如我在你的代理沙箱只是个建议一文中所说,我们以为存在于可信代码与不可信代码之间的那道边界,远比大多数团队想象的要疏漏得多。

攻击者的质量曲线

软件质量随迭代而提升。这条规律对攻击者和防守方同样成立。TeamPCP 的行动一周之内打穿了五个生态:GitHub Actions、Docker Hub、npm、Open VSX 和 PyPI。4 每攻陷一个生态,用的都是从上一个生态里收割来的凭据。整场行动的操作水准相当老练:在投递载荷前 24 小时注册域名,对可变引用做标签劫持,还利用 Aqua Security 密钥轮换不彻底的漏洞绕开了凭据轮换。

在一场本来颇为老练的行动里,fork 炸弹是唯一的失手。下一场行动不会再犯这个错。.pth 文件技术如今已被公开记录,CrowdStrike、Microsoft、Wiz 和 Palo Alto 都做过分析。3 下一个攻击者继承的是这套技术,但不带那个 bug。

攻击能力和防御能力沿着同一条曲线成长。技术是公开的,分析也是公开的。下一个攻击者的起点,就是 TeamPCP 的终点。关于这条曲线对自主系统意味着什么,我在无人监督时真正会出问题的地方里展开谈过。

检测不能寄望于攻击者失手

当前的供应链检测模型分三层,面对 LiteLLM 这三层全军覆没:

静态分析没查出来。.pth 文件是 Python 的正规特性。载荷经过两层 base64 编码,运行时才解码。那些靠已知恶意特征匹配的静态扫描器一无所获,因为这个特征是新的。

行为监控没查出来。凭据窃取程序只发了一个出站 HTTPS POST 请求,目标域名看着像个正经服务(models.litellm.cloud)。检查目标域名的出站监控,得知道这个特定域名是 24 小时前才注册的才行。而大多数出站监控根本不查域名年龄。

代码审查没查出来。恶意版本是直接发布到 PyPI 的,整条 GitHub CI/CD 流水线被完全绕过。没有 pull request 可审,没有 diff 可看。攻击者用盗来的发布凭据上传了预先构建好的软件包。

每一层检测都以为攻击链的另一环会兜住问题。结果谁也没兜住。兜住问题的是那个 fork 炸弹。

什么才真能发现无声的恶意代码

既然不能指望攻击者失手,就需要那些不依赖实现质量的检测手段。

对出站请求做域名年龄检查。 外传域名是在攻击前 24 小时注册的。一条防火墙规则,把发往注册不足 7 天的域名的出站请求标记出来,本可以发现这次攻击。规则很简单,误报率也在可控范围内,而且它正好覆盖了最常见的外传模式。

为 Python 进程建立行为基线。 一次 pip install 突然向陌生域名发起 HTTPS POST 请求,这本身就反常。进程级的行为监控只要跟踪软件包安装期间的网络活动,就能把它标出来。

文件系统诱饵文件。 在一个诱饵路径下放一把假的 SSH 密钥,另一个诱饵路径下放一份假的 AWS 凭据,然后监控是否有进程读取这些文件。凭据窃取程序会把标准路径扫一遍,必然读到诱饵;正常进程则不会。诱饵会在外传完成之前触发告警。

安装环境隔离。 在一个拿不到真实凭据的环境里执行 pip install,装完之后再把软件包复制到生产环境。.pth 文件是在 pip 自己的 Python 进程中触发的,也就是说凭据窃取程序在安装阶段就已经跑了。只要安装环境里没有凭据可偷,这次攻击就什么也拿不走。

以上这些手段,没有一项需要攻击者失手,它们与载荷质量无关,照样奏效。这种架构思路——把环境设计成即便攻击完美无缺也一无所获——和部署与防御:代理信任悖论背后的原则是同一个。

不对称之处

防守方有一个结构性优势:环境由防守方来定。攻击者只能在软件包被装进去的那个环境里施展。如果那个环境没有凭据、没有网络访问权限,还布着文件系统诱饵文件,那么载荷在技术上成功了,在实际效果上却是彻底失败。

LiteLLM 这次攻击之所以奏效,是因为安装环境和存放发布凭据、SSH 密钥、云令牌的,是同一个环境。fork 炸弹对安全架构本身毫无影响,它影响的只是暴露的时间早晚。

下一次,fork 炸弹不会再出现。而凭据依然会和包管理器待在同一个环境里。问题在于:在下一个攻击者交付一份干净的载荷之前,你有没有把环境改掉。我在 Ralph 代理架构的分析里讲了如何组织代理系统,让被攻陷的组件无法越过自身的隔离边界扩大战果。


常见问题

攻击者为什么没测出这个 fork 炸弹?

.pth 文件派生一个子进程来跑载荷,本身是个合理的实现选择,可以避免阻塞父进程。递归触发源于 .pth 与 Python 的 site.py 初始化之间一处微妙的相互作用。这类 bug 通常在集成测试中才会暴露,单元测试里看不出来,而恶意代码作者很难在贴近真实的环境中做集成测试。

fork 炸弹会不会是故意的?

可能性很小。fork 炸弹让恶意代码立刻暴露,这与攻击者的目标背道而驰。一个安静运行数周的凭据窃取程序,收割到的凭据比一个 46 分钟内就被发现的要多出好几个数量级。

域名年龄检查在大规模场景下可行吗?

可行。域名年龄可以通过 WHOIS 或 DNS 注册日期 API 获取。这项检查给每个请求增加的延迟只有毫秒级。大多数组织都可以把已知的新域名加入白名单。


参考来源


  1. FutureSearch (Daniel Hnyk), “LiteLLM Hack: Were You One of the 47,000?” March 2026. 

  2. isfinne et al., “LiteLLM Supply Chain Attack,” GitHub Issue #24512, March 2026. 

  3. Blake Crosley, “The Supply Chain Is the Attack Surface,” blakecrosley.com, March 2026. 

  4. Kaspersky, “Trojanization of Trivy, Checkmarx, and LiteLLM Solutions,” March 2026. 

  5. Blake Crosley, “When Your Agent Becomes the Researcher,” blakecrosley.com, March 2026. 

相关文章

你的Agent中间商你从未审查过

研究人员测试了28个LLM API路由器。17个接触了AWS金丝雀凭证。一个从私钥中抽走了ETH。路由器层是新的攻击面。

1 分钟阅读

AI供应链攻击:供应链就是攻击面

Trivy通过标签劫持遭到入侵,随后是PyPI上的LiteLLM,46分钟内47,000次安装。AI供应链完全按设计运转。

3 分钟阅读

Ralph循环:我如何在夜间运行自主AI代理

我构建了一个使用停止钩子、生成预算和文件系统记忆的自主代理系统。以下是失败经验以及真正能交付代码的方法。

3 分钟阅读