LiteLLM供应链投毒事件:40分钟窃取全栈凭证的深度复盘

一次持续40分钟的供应链攻击
2026年3月24日,两个恶意版本的 LiteLLM——1.82.7 和 1.82.8——被推送到 PyPI,短暂上线约40分钟后即被撤下。时间虽短,但破坏力惊人。这次攻击是更大规模「TeamPCP」行动的一部分,其源头可追溯到一枚泄露的 Trivy 自动化令牌(automation token)。
软件供应链攻击是近年来最具威胁性的攻击向量之一——攻击者不直接攻击目标系统,而是通过污染目标所依赖的上游组件来实现入侵。PyPI 作为 Python 生态最大的第三方包仓库,日均下载量超过10亿次,任何被污染的包都可能在极短时间内扩散到大量生产环境。Trivy 是 Aqua Security 开发的开源漏洞扫描器,广泛用于 CI/CD 流水线中对容器镜像和依赖进行安全审计。攻击者获取 Trivy 的自动化令牌后,得以在官方维护流程中注入恶意代码——这种从安全工具本身入手的攻击路径格外讽刺且有效。
TeamPCP 是一个针对开源生态的有组织攻击行动,其手法与2021年的 ua-parser-js 劫持、2022年的 node-ipc 投毒事件一脉相承。这类攻击的核心逻辑是利用开源维护者的凭证管理漏洞(如 API 令牌泄露、双因素认证缺失)获取包的发布权限,然后在合法包中注入恶意代码。PyPI 在2023年曾因类似威胁短暂关闭新用户注册和新包上传功能。Trivy 自动化令牌的泄露路径可能涉及 GitHub Actions 的密钥管理不当或 CI 日志中的意外输出——这是开源项目 CI/CD 流水线中反复出现的安全盲区。
有意思的是,FBI 已就此事发布 FLASH 通告(TLP: CLEAR,可公开分享),说明该事件已进入官方安全响应视野。FBI 的 FLASH 通告是联邦调查局用于快速向私营部门传递网络威胁情报的机制,TLP:CLEAR 标记意味着信息可无限制公开分享,这表明该事件的严重程度已触发国家级响应。对于任何在生产环境中运行 LLM 网关的团队而言,这不是一次可以轻描淡写的插曲,而是一堂关于软件供应链安全的硬核课程。

攻击机制:Python .pth 文件为何如此危险
这次攻击真正值得每个 MLOps 工程师警惕的,是它的技术实现方式。
绕过 import 的静默执行
恶意包中携带了一个 .pth 文件。与普通 Python 模块不同,.pth 文件会在解释器启动时被执行,而非在代码 import 时才触发。
要理解这一机制的危险性,需要了解 Python 的启动流程:CPython 在初始化 site 模块时会遍历 site-packages 目录下所有 .pth 文件,逐行解析——普通路径行被添加到 sys.path,而以 import 开头的行则通过 exec() 直接执行。具体执行路径为:Python 启动 → Py_Initialize() → initsite() → site.main() → site.addsitedir() → 遍历 site-packages 下所有 .pth 文件 → 对以 import 开头的行调用 exec()。这个执行发生在 __main__ 模块加载之前,甚至在 PYTHONSTARTUP 脚本之前。值得注意的是,virtualenv 和 conda 环境同样受此影响,因为它们各自维护独立的 site-packages 目录但共享相同的 .pth 处理逻辑。这个机制存在于 Python 2.x 时代至今,最初是为了方便包的初始化配置(例如 setuptools 的 .pth 文件用于启用 pkg_resources),但它实际上提供了一个在任何用户代码运行之前就能执行任意代码的入口点。PEP 648 曾提议限制 .pth 文件的代码执行能力,但至今未被采纳,这意味着这一攻击面在可预见的未来仍将存在。
这意味着:
- 你的代码是否真正调用过
litellm,完全无关紧要; - 只要包被安装,且任何一个 Python 进程启动,恶意载荷就会运行;
- 大多数安全扫描器把「import 时」当作检测节点,而
.pth载荷早在此之前就已执行完毕。
这种设计让传统的运行时检测几乎失效。像 Bandit 这样的静态分析工具和基于 import hook 的运行时监控方案都难以捕获此类攻击,因为恶意代码的执行路径完全绕过了它们的检测节点。攻击者精准利用了 Python 生态中一个长期被忽视的执行入口。
窃取目标:API Key、云凭证和SSH密钥
载荷运行后,会收集环境变量、SSH 密钥、云凭证、Kubernetes 服务账户令牌,以及各类供应商 API Key。在容器化的 MLOps 环境中,这些信息往往通过环境变量注入或挂载 Secret 的方式提供给应用进程——这种设计虽然符合十二要素应用的最佳实践,但也意味着任何在进程内存空间中运行的代码都可以通过 os.environ 或读取文件系统来获取全部敏感信息。
在 Kubernetes 环境中,Secret 通常通过两种方式注入 Pod:环境变量注入(通过 envFrom 或 env.valueFrom)和卷挂载(通过 projected volume 或 CSI driver)。十二要素应用方法论推荐使用环境变量配置应用,但这意味着通过 /proc/self/environ 或 os.environ 即可读取所有注入的密钥。即使使用挂载方式,Secret 默认以 tmpfs 形式存在于 Pod 文件系统的 /var/run/secrets 路径下,任何在容器内运行的代码同样可以读取。Kubernetes RBAC 只控制谁能通过 API 创建/读取 Secret 对象,但无法限制 Pod 内部进程对已注入 Secret 的访问——这是容器安全模型中一个常被误解的边界。
为什么LiteLLM是MLOps中价值最高的攻击目标
发帖者(来自 CI/CD 安全公司 InvisiRisk)指出了一个关键洞察:LiteLLM 的设计定位,决定了它是整个 ML 技术栈中价值最高的凭证窃取着陆点。
LiteLLM 是一个开源的 LLM API 统一代理层,它将 OpenAI、Anthropic、Google Vertex AI、AWS Bedrock、Azure OpenAI 等数十个模型提供商的 API 统一为 OpenAI 兼容格式。在典型的 MLOps 架构中,LiteLLM 充当反向代理角色:所有内部应用只需与 LiteLLM 通信,由它负责路由、负载均衡、速率限制和成本追踪。这种架构设计借鉴了微服务中 API Gateway 的理念(如 Kong、Envoy),但在 AI 场景下有一个关键区别——它必须持有所有下游服务的认证凭证。
作为一个统一网关,LiteLLM 天生就「坐在所有模型服务前面」。也就是说,这一个进程往往同时持有:
- 你的 OpenAI Key
- 你的 Anthropic Key
- 你的 Bedrock 凭证
- 以及所有经由它路由的其他 Provider 密钥
传统 API 网关通常依赖下游服务自身的认证机制,而 LLM 网关由于需要代理 API 调用,必须在自身进程内存中保存所有 Provider 的密钥。换句话说,攻破 LiteLLM 一个点,等于一次性拿到整个组织的模型访问权限。而在那致命的40分钟里,它不仅是最高价值目标,也是最容易得手的目标。这正是 AI 基础设施集中化带来的安全悖论:便利性与攻击面成正比。
从攻击者的投入产出比来看,选择 LiteLLM 作为目标堪称完美:它在 GitHub 上拥有超过 15,000 颗星,被大量企业用于生产环境,且其核心用户群(AI 工程团队)通常比传统安全团队更倾向于快速迭代而非严格的依赖审计——这使得恶意包在短时间窗口内被大量安装的概率极高。
事后排查:为什么常规检测手段几乎都失效
真正棘手的问题是:几个月过去了,你还能确认自己当时是否安装了恶意版本吗?发帖者坦言,大多数直觉性的检查手段在这里都不奏效。
宽松版本锁定的陷阱
如果你的依赖声明是宽松的,比如 litellm>=1.82,而恰好有一次构建发生在那个时间窗口内——那么你很可能中招了。
Python 生态中的依赖管理存在多个层次:requirements.txt 中的宽松声明仅表达兼容性约束,而 lock 文件(如 pip-tools 生成的精确 requirements、Poetry 的 poetry.lock、PDM 的 pdm.lock)则记录了某次解析时确定的精确版本和哈希值。问题在于,许多团队仅保留宽松声明而不提交 lock 文件到版本控制,或者虽然使用 lock 文件但在 CI/CD 中允许自动更新。更关键的是,即使保留了 lock 文件,Docker 镜像构建过程中的实际安装行为可能因缓存层和构建时间的不同而产生差异。pip 的依赖解析器在面对同一组约束时,可能因 PyPI 索引的实时状态(哪些版本可用、哪些已被 yank)而产生不同结果——这意味着同一份 requirements.txt 在不同时间点构建可能安装完全不同的版本。
已解析依赖清单被丢弃
更糟的是,已解析的依赖清单(resolved manifest)通常在构建后就被丢弃。已解析清单是指构建时 pip/poetry 实际下载安装的包及其精确版本的完整记录。于是「我们在3月24日到底安装了什么版本」这个问题,几个月后往往无法回答。这暴露了许多团队在构建产物留存与可审计性上的普遍缺失——也正是软件物料清单(SBOM, Software Bill of Materials)概念试图解决的核心问题。
SBOM 旨在为每个软件构建产物生成一份完整的成分清单,使得任何时刻都能回溯「这个制品里到底包含了什么」。SBOM 已从行业倡议上升为合规要求——美国2021年的14028号行政命令要求向联邦政府销售软件的供应商必须提供 SBOM。主流 SBOM 格式包括 SPDX(Linux Foundation 维护)和 CycloneDX(OWASP 维护),两者都支持记录包名、版本、哈希值、许可证和依赖关系。在容器场景中,Syft 可以对已构建镜像进行逆向分析生成 SBOM,而 Grype 则基于 SBOM 进行漏洞匹配。将 SBOM 集成到 CI/CD 并存储到制品仓库(如 Harbor、JFrog Artifactory)中,可确保任何历史构建的依赖组成都可追溯。
两种排查思路:版本历史与凭证暴露检测
针对这次事件,帖子提到了一个更高效的方法,并厘清了两类不同问题的区别。
问题一:我是否拉取了恶意包?
这要靠版本历史来回答——查 lock 文件历史、查 registry 拉取日志。但如前所述,这些记录常常已不可考。值得注意的是,PyPI 本身不向普通用户提供包下载历史查询接口,Google BigQuery 上的 PyPI 下载数据集虽然记录了每次下载的时间戳和版本号,但需要与自身 CI/CD 的构建时间进行交叉比对才能确定是否受影响。
问题二:我的密钥是否真的被窃取?
CloudSEK 针对此事件公开了一个查询工具(exposure.cloudsek.com/ai-supply-chain-incident)。它回答的是一个更接近结果的问题:你的密钥是否出现在攻击者实际收集到的数据中。据发帖者描述,这是一个「30秒即可完成」的检查。CloudSEK 是一家总部位于印度的网络威胁情报公司,其数据来源通常包括暗网监控、Telegram 频道爬取和蜜罐捕获——这意味着该工具的查询结果基于对攻击者外泄数据的实际分析,而非推测性评估。
需要强调的是:即便查到命中,也不等于确认被入侵。FBI 通告同样指出——发现了恶意依赖,并不能证明恶意代码真的运行过。命中应被视为「深入调查的理由」,而非「事件本身的结论」。
给MLOps团队的实操防御建议
这次事件为所有运行 AI 网关的团队提供了清晰的行动清单:
- 立即核查时间窗口:如果你使用宽松版本锁定,回溯3月24日前后的构建记录。
- 善用暴露查询工具:在无法重建 lock 历史时,用凭证暴露查询快速判断风险等级。
- 假设即轮换:许多团队的务实做法是——与其花时间做「lock 文件考古」,不如直接轮换所有可能暴露的凭证(API Key、SSH 密钥、云凭证等)。凭证轮换(credential rotation)是安全响应的核心动作,在无法确定影响范围时,假设最坏情况并全面轮换是业界公认的最佳实践。需要注意的是,不同 Provider 的密钥轮换复杂度各异:OpenAI 和 Anthropic 支持在 Dashboard 中即时生成新 Key 并废弃旧 Key,而 AWS IAM 密钥的轮换可能涉及多个服务的配置更新,需要通过基础设施即代码(IaC)工具统一管理以避免服务中断。
- 收紧依赖锁定策略:使用精确版本 pin 和完整的 lock 文件,并长期留存已解析清单,让未来的审计成为可能。考虑在 CI/CD 流水线中集成 SBOM 生成工具(如 Syft、CycloneDX),为每次构建自动生成可追溯的依赖清单。进一步的防御措施包括:启用 pip 的
--require-hashes选项确保下载的包与预期哈希完全匹配、配置私有 PyPI 镜像(如 Artifactory、DevPI)作为缓冲层以便在上游出现恶意包时快速隔离、以及在 CI 中使用pip-audit或safety对每次依赖解析结果进行已知漏洞扫描。 - 重新评估网关的凭证隔离架构:既然单个网关持有全栈密钥,就应考虑最小权限、密钥隔离与短时凭证等纵深防御措施。具体而言,可采用 HashiCorp Vault 等密钥管理系统的动态密钥功能,为每次 API 调用生成临时凭证;利用 AWS STS 临时凭证(默认有效期1小时)或 Google Cloud Workload Identity Federation 替代长期静态密钥;为每个下游 Provider 使用独立的、权限受限的服务账户,确保单点泄露不会导致全面沦陷。传统静态 API Key 的最大风险在于其无限期有效且通常拥有完整权限——一旦泄露,攻击者可在密钥被发现和撤销之前的整个窗口期内无限制使用。动态密钥方案通过 Vault 的 Secrets Engine 实现按需生成和自动过期,而 AWS IRSA(IAM Roles for Service Accounts)则让 Kubernetes Pod 无需持有任何静态密钥即可获取15分钟到12小时有效期的临时凭证。对于 OpenAI 等尚不支持短期令牌的 Provider,可通过在网关前部署密钥代理(如 Infisical、Doppler)实现密钥的自动轮换和访问审计。
结语:AI基础设施集中化的安全代价
LiteLLM 事件是 AI 基础设施安全的一个缩影。当我们把所有模型访问集中到一个统一网关以追求便利时,也在无意中打造了一个「一击致命」的攻击目标。40分钟的窗口,.pth 的静默执行,以及事后难以重建的依赖历史,共同构成了现代 ML 供应链的脆弱一面。
这一事件也折射出整个云原生生态面临的结构性挑战:我们在追求开发效率的过程中,构建了越来越深的依赖链条和越来越集中的信任点。从 SolarWinds(2020年,通过构建系统注入后门,影响18,000+组织)到 Log4Shell(2021年,一个日志库的漏洞影响了全球数十亿设备),再到 Codecov(2021年,Bash Uploader脚本被篡改导致大量企业CI环境凭证泄露),每一次供应链攻击都在提醒我们——现代软件的安全边界早已不是自己写的那几行代码,而是整个依赖图谱中最薄弱的那个环节。
行业正在尝试从根本上解决这一问题:SLSA(Supply-chain Levels for Software Artifacts)框架定义了从源码到制品的完整信任链要求;Sigstore 项目提供了无密钥的代码签名方案,使包的来源验证成为可能;而 OpenSSF(Open Source Security Foundation)的 Scorecard 项目则试图为每个开源项目的安全实践提供可量化的评分。但这些方案的大规模采用仍需时间,在此之前,每个 MLOps 团队都需要为自己的依赖链安全承担主要责任。
真正的问题或许不是「这次谁中招了」,而是「下一次我们能否更快地知道」。
相关推荐

Claude Code创建者建议:大改动别急着写代码,先对齐再动手
Claude Code创建者Boris分享AI编程协作最佳实践:面对大改动,先读仓库提问、确认方案再编码、写完立刻验证。掌握这套流程,避免AI沿错误方向返工,提升编程效率。

HydraNet-VSM架构解析:Mamba与注意力机制并行融合的推理新思路
深入解析HydraNet-VSM混合架构设计提案,探讨Mamba状态空间模型与Attention注意力机制并行融合方案,以及Verified Step Memory验证循环如何解决思维链推理不忠实问题。

Seed7编程语言:无GC实现内存安全的独特设计
深入解析Seed7编程语言如何在不依赖垃圾回收(GC)的情况下实现内存安全,探讨其AOT编译、可扩展语法、整数溢出检查等核心特性,以及与C++、Rust、Java等主流语言的对比。