LiteLLM 1.107.0 供应链投毒:启动 Python 就偷密钥,两小时窗口如何酿成网关大劫
2026年10月21日,LiteLLM 1.107.0 被投毒后上了 PyPI 与 Docker:装包后不用 import 也会触发 cook.pth 与 sitecustomize.py 偷走云、SSH、浏览器与网关数据库密钥。官方、微软与 Wiz 的拆解拼出了完整攻击链与处置清单,安全版本是 1.106.2。

先划清界线:这篇不是旧闻重炒
本站此前出现过两次 LiteLLM,但都不是这件事。一篇是 10 月 7 日的产品新闻,讲 LiteLLM 发布的开源云端 coding agent Moyai,核心是它号称比 Devin 便宜 79% 的成本账;另一篇是一周运行时爆雷全景里顺带提了一句 LiteLLM 投毒,那只是一条快讯级别的线索。本篇是完整的事故深挖:只讲 2026 年 10 月 21 日当天被投毒的 LiteLLM 1.107.0,从它怎么进发布流水线、怎么在你启动 Python 的一瞬间偷密钥、到事后该怎么收拾,全部展开。如果你只想知道一句话结论,那就是:装过 1.107.0 的环境先别急着删包重装,第一步是别再在这个环境里跑任何 Python。
两小时窗口,毒包上了 PyPI 和 Docker
2026 年 10 月 21 日上午,LiteLLM 的发布流水线被攻破,恶意版本 1.107.0 被推上 PyPI 和容器镜像仓库。官方通报的时间线很精确:PyPI 上的 7 个 wheel 从世界标准时间 11:39:51 存活到 13:23,Docker Hub 的 v1.107.0、latest、main-latest、main-stable 四个标签从 11:46:56 指向被投毒的镜像索引,直到 14:20 才全部清除或重定向到干净镜像;GHCR 上同名标签则在 13:51 前已不可用。Wiz 的口径略有出入,称 PyPI 在 13:20 隔离了该版本,两者相差几分钟,不影响结论:这是一个以小时计的短窗口,但已经足够长。Wiz 引用 ClickPy 数据称恶意版本约有 49,000 次下载——请注意,这是下载量,不是受害者数量,有多少环境真正启动过毒包、多少密钥真正外泄,目前没有任何一方给出确数。
更值得警惕的是官方特意澄清的一点:GitHub 上官方的 v1.107.0 源码标签本身并不含恶意文件。毒代码来自攻击者控制的仿冒仓库,发布流程被劫持后才把毒轮子签发出去。也就是说,只看源码仓库、只看版本号,你看不出问题;问题出在构建与发布那一环。这也是为什么事后官方反复强调:安全版本是 1.106.2,以及此前任何已发布的明确版本,而不是 latest 这类浮动标签。
最阴险的一点:不用 import LiteLLM 也会中招
这次投毒的杀伤力不在于它藏得多深,而在于它触发得多早。恶意 wheel 会把三个文件直接铺到 Python 环境的 site-packages 根目录:cook.pth、cook.py 和 sitecustomize.py。Python 解释器启动时做站点初始化,就会先处理 .pth 文件里的可执行行,再尝试导入 sitecustomize 模块——整个过程发生在你的应用 import 任何东西之前。
具体是两条互相备份的启动链。第一条从 cook.pth 里的一行 import cook 开始,加载 cook.py;cook.py 把内嵌的 Base64 加 zlib 压缩代码解开,跑起一个自称 PRIVCOLLECT 的凭证收集器,通常以守护线程在后台工作。微软的分析指出,一个在 Windows 上用 Python 装无关依赖的普通操作就触发了收集器,这正是它最危险的地方:受害者完全不需要正在用 LiteLLM。第二条是面向 Unix 系统的 sitecustomize.py,它会脱离成后台守护进程,持有一个锁文件,并定期去读一个托管在 GitHub 上的 domain.txt 文件——那是一个可变的死信箱地址指针。如果指针指向的服务器有响应,它就会去拉取一段代码并直接执行。但必须说清楚:截至研究人员检查时,domain.txt 是空的,第二阶段拉取与执行没有被证实。这是一个可随时重新武装的后门,不是一次已经发生的二次攻击,不要把两者混为一谈。
它到底偷什么:几乎是你机器上所有能叫出名字的密钥
微软和 Wiz 对收集器的拆解高度一致,清单长得让人不舒服。云与工作负载一侧:AWS、Azure CLI、Google Cloud 的配置与凭证缓存,Kubernetes 的服务账号文件,Docker 配置,挂载的 secret,以及 AWS 和 GCP 的实例元数据服务。开发者工具一侧:Git 凭证、包管理器配置、dotenv 文件、私钥扩展名文件、AI 助手与 IDE 的认证文件,包括 GitHub Copilot、VS Code 和 MCP 集成。浏览器一侧:Chromium 系与 Firefox 的凭证相关数据和浏览历史,外加 shell、数据库客户端和交互工具的历史记录。进程环境一侧更狠:在 Linux 上它会读 procfs 里其他进程的环境变量,也就是说,哪怕某个密钥不是给 LiteLLM 进程配的,只要同机进程能读到,就可能被连带偷走。
而对 LiteLLM 网关本身,攻击者还留了专杀招。收集器会在满足条件时拉起一个 Python 子进程,用 Prisma 直接读代理数据库的两张表,每张表限 500 行,目标就是代理里存的上游供应商密钥和模型配置。Wiz 给出的查询形态是直接查 LiteLLM 凭证表并取 500 条。换句话说,LiteLLM 网关这个产品的卖点——把所有模型供应商的密钥集中到一个网关里统一管理——在这次事件里恰好变成了最大的 blast radius:一个网关被偷,等于它代理过的所有供应商密钥一次性暴露。
偷到的数据怎么运出去,微软也拆得很细:每次运行生成新的 32 字节 AES 密钥和 16 字节初始化向量,用 AES-256-CBC 加密收集结果,再用内嵌的 4096 位 RSA 公钥把 AES 密钥包起来,打成 gzip 压缩包,分 data.bin、key.bin、hdr.json 三个成员,用一次 HTTP POST 发到攻击者控制的仿冒域名 www[.]litellm[.]dev 的 healthcheck 路径。这个域名长得像 LiteLLM 官方基础设施,但它是攻击者的,与官方域名只差一个字母级别的迷惑性——顺带提醒一句,不要在任何地方不加防改写地照抄这个地址去访问。
攻击链:三个工程师的 GitHub CLI 令牌,和一个长得极像的仓库名
官方在 10 月 21 日晚间的通报里拼出的攻击链,读起来像一部权限管理反面教材。先是发布权限在周一被临时放宽,允许两名内部工程师发布每日 bugfix 回滚版本;攻击者手里已有三名开发者的 GitHub CLI OAuth 令牌,其中一个能跑 GitHub Workflow。攻击者先试图改发布流程、把 releaser 指到别的源码仓库,被分支保护拦下一次;随后用属于组织所有者之一的令牌强行关闭分支保护、强制推送改动;再用另一名开发者的凭证通过生产发布审批门;最终跑起被改过的 workflow,把它指向仿冒仓库 Berri-AI/litellm——注意连字符,真正的组织名是 BerriAI,没有连字符——毒轮子就是这么被官方签名流程签发出去的。事后攻击者还用第三名开发者的凭证删了生产与 staging 的 workflow 记录,试图抹掉调查线索。
那么令牌最初怎么丢的?官方目前的说法仍带“相信”“怀疑”这类限定词,没有盖棺定论。官方观察到,在特定情况下 Claude Code 会把用户的 GitHub CLI OAuth 令牌嵌进响应体,而如果响应体被记日志,令牌就会出现在 LiteLLM 日志里;官方还发现自家一个暴露在公网的内部 LiteLLM 部署里有个名字不起眼的账号,怀疑攻击者正是通过这个部署拿到日志里的令牌,并怀疑入侵口是一个近期刚修补的漏洞。微软在 10 月 23 日的文章里也明确表态:没有独立确认攻击者最初如何进入上游环境或拿到这些凭证。归因尚未确认,任何把这次事件钉死在某个组织或国家身上的说法,目前都超出了证据。
事后利用的证据倒是有一部分被 Wiz 真实观测到了。Wiz 称,在外泄约 4.5 小时后,也就是 10 月 21 日 16:20 至 17:01 UTC 之间,攻击者开始验证从受害 CI runner 和 Kubernetes 节点偷来的 AWS 实例角色凭证,动作是先调 sts:GetCallerIdentity,再调 bedrock 的 ListFoundationModels,模式符合 LLMjacking——偷云凭证去刷大模型调用。少数环境里还出现了 IAM 枚举和通用发现动作。这是已观测到的滥用,不是推测;但它只证明“部分被盗凭证被试过”,不等于所有下载者都被盗,也不等于损失规模已知。
如果你在窗口期装过:先停手,再按顺序收拾
官方和微软给出的处置顺序有一个共同前提,而且反直觉:不要先在可疑环境里跑 python 或 pip 来“查一下”。因为任何一次 Python 启动都会重新执行 cook.pth 和 sitecustomize.py,等于亲手再触发一次收集器。正确的顺序是先隔离:停掉相关 Python 进程和自动化,不要让它重启;从一台已知干净的机器、用可信工具从外部检查可疑环境,而不是在里面自查。
接下来是清理与重建。官方建议从每一个受影响的 site-packages 里删掉 cook.pth、cook.py、sitecustomize.py 以及对应的 pyc 缓存,杀掉持有锁文件的脱离守护进程,然后钉死到 litellm==1.106.2 重装;更稳妥的是整个虚拟环境或镜像从头重建,而不是只删 litellm 目录——因为启动文件在 litellm 应用目录之外,只删应用目录等于没清干净。微软的建议更进一步:把受影响的包、缓存、内部镜像、lock 文件、构建产物和容器镜像全部找出来比对哈希,优先用镜像 digest 而不是浮动标签来确认到底跑的是哪一份内容。
最后也是最疼的一步:轮换所有可疑环境能触及的凭证。官方清单包括实例或任务 IAM 角色、代理数据库里的全部供应商密钥和主密钥与虚拟密钥、DATABASE_URL 的密码、宿主机上任何进程环境里出现过的 secret;微软补充了 CI/CD secret、源码与镜像仓库令牌、SSH 与 GPG 密钥、AI 编程工具令牌和浏览器会话,并且提醒只换密钥不等于吊销会话——已经签发的会话要主动吊销。还要去查 CloudTrail,看安装时间点附近有没有来自实例角色的 GetCallerIdentity,以及之后有没有在自家基础设施之外使用该角色凭证的记录。企业客户走私有注册表的版本不受影响,这一点官方已明确,不必跟着恐慌。
给 vibe coder 的判断:这次事件真正改变了什么
我的判断是,这次事件把“AI 网关”这个品类从便利工具重新定义成了高价值目标。以前我们谈供应链安全,谈的是某个小众包被投毒、某个依赖被抢注;这次被投毒的是一个被大量团队放在所有模型调用必经之路上的网关,而网关天然集中了所有上游密钥、数据库连接串和租户策略。攻击者不需要攻破你的业务代码,只要攻破网关的发布流程,就能一次性拿到一整批团队的云凭证和模型密钥。这种“单点集中、单点失守”的结构,在 vibe coding 场景里尤其普遍:一个人用一个网关串起五六个模型供应商,图的是省事,代价是把全部身家放在一个篮子里。
所以有几条做法应该从“建议”升级成“默认”。第一,永远钉版本、钉哈希:pip 装包写死版本号,Docker 用 digest 而不是 latest,latest、main-latest 这类浮动标签在这次事件里恰恰是重灾区。第二,网关和开发机分开:不要在每天跑各种实验、装各种包的开发机上同时跑存着全量供应商密钥的生产网关;CI runner 和 Kubernetes 节点这类高权限环境更要单独隔离,因为 Wiz 观测到的第一批被验证的凭证正是从这些地方偷来的。第三,密钥分层:给网关用的云角色只给最小权限,别让一个网关进程既能读数据库、又能调云 API、还能访问所有供应商密钥;一旦出事,轮换范围就是整个公司。第四,镜像与包先过内部镜像审查再进生产,而不是让开发机和 CI 直接从公共源拉取浮动版本。
还有一个更深的教训,藏在攻击链的起点里。令牌泄露的疑似路径是 AI 编程工具把凭证嵌进响应体、又被日志记下来——也就是说,AI 工具链本身正在成为新的凭证泄露面。你用 AI 工具帮你排查问题、看日志、跑命令的时候,工具看到的上下文里可能就有令牌,而这些上下文又可能被另一个系统记下来。以后凡是把 AI 工具接进内部系统、尤其是接进存密钥的网关时,都要先问一句:它的输出会不会被记日志?日志谁能看?这个问题的答案,在 10 月 21 日之前很少有人认真问过,之后必须成为接 AI 工具的默认检查项。
一分钟自查:你到底在不在窗口里
很多读者看到这里最想问的其实是同一句:我到底中没中?判断标准其实很具体,不需要猜。先看时间:只有在 10 月 21 日世界标准时间 11:39 至 13:23 之间用 pip 装过未钉死版本的 LiteLLM,或者在 11:46 至 14:20 之间起过 v1.107.0、latest、main-latest、main-stable 任一容器镜像,才算进入过窗口。装包时间不在窗口里、版本钉死在 1.106.2 或更早明确版本、走企业私有注册表的,都不在官方认定的受影响范围。其次看痕迹:用干净的机器从外部检查可疑环境的 site-packages 里有没有 cook.pth、cook.py、sitecustomize.py,有没有以 .pc_e_ 开头的 OpenSSL 临时目录,有没有 /tmp/remote_code_loader.lock 这个锁文件和 /tmp/.pc_once_72129118c59f5636c0bb 这个标记文件。找到启动文件等于确认暴露过,但没找到不等于干净——容器重建和临时目录清理都可能抹掉痕迹,而官方和微软都反复提醒过这一点。最后看出向流量与云侧记录:检查安装时间点附近有没有指向仿冒 healthcheck 地址的 POST,有没有 CloudTrail 里来自实例角色的异常 GetCallerIdentity。任何一项对上,都按“已外泄”处理,不要按“可能没事”处理。
还有一个容易被忽略的角落:缓存和镜像。即使你已经把生产环境的包换成 1.106.2,只要内部 pip 镜像、uv 缓存、CI 构建缓存或某个旧镜像层里还躺着 1.107.0 的轮子或镜像层,下一次构建就可能把它重新拖回来。微软的建议里特意把缓存、镜像、lock 文件和构建产物全部列进排查范围,正是针对这种“删了生产、漏了缓存”的复活路径。自查清单的最后一项,应该是把这些地方也扫一遍,并且把 latest 这类浮动标签从所有部署配置里清掉,换成写死的版本号加 digest。做完这一轮,你才算真正关上了这扇门,而不是只关上了正门、却给后门留了钥匙。
最后说一句不那么技术、但很重要的话。这次事件里官方的通报节奏值得肯定:事发当天就给出受影响版本、时间窗口、检查方法和处置清单,几天后又主动标注早期假设中后来被发现不准确的部分,并指向一份 canonical 记录。这种“先给可行动的信息、再修正、不藏着”的做法,是供应链事件里最难得的品质。对比之下,那些只发一句“我们已注意到问题”的通报,才是真正让用户没法自救的。选择依赖的时候,除了看 star 数和功能,也值得看一眼这个项目出事时会不会这样说话——因为下一个 1.107.0 迟早会来,区别只在于你到时手里有没有一份能立刻执行的清单。
评论 (0)
原始来源
相关文章

10 月 24 日多家媒体集中介绍 Anthropic 的 sandbox-runtime:用操作系统原生机制给任意进程加文件与网络限制,不需要容器。但它的仓库 2025 年就已公开,读默认放开、Windows 还是 alpha、84% 只是内部自报的提示减少数——这篇先把这些边界讲清楚,再给落地清单。

微软 10 月 19 日开源 Scope:MIT 协议、跑在 Kubernetes 上,不给模型排名,而是给你自己的产品做重复实验——同一任务、同一标准,改一份文档再跑一次,看 agent 行为变没变。Cosmos DB 案例的数字只在 8 个被测场景内成立,Judge 会判错、结果会波动,人工复核不可少。

GitLab 10 月 21 日修复 CVE-2026-110264:正则引擎 Onigmo 整数溢出,未登录也可能在自管服务器上执行代码,CVSS 满分 10.0,影响从 7.0 起的全部版本。10 月 12 日刚打过补丁的实例仍在范围内,修复版本恰好高一级;两条检测规则与升级停机成本一并讲清。