LiteLLM 供应链攻击完整拆解:从 Trivy 失陷到 PyPI 投毒 的文章头图

LiteLLM 供应链攻击完整拆解:从 Trivy 失陷到 PyPI 投毒

根据 The CyberSec Guru 的原文技术拆解转写,梳理 2026 年 3 月 LiteLLM 供应链攻击的完整链路:Trivy GitHub Actions 失陷、PyPI 发布凭证泄露、1.82.7 与 1.82.8 恶意版本投毒、.pth 自动执行,以及团队应该如何应急止损。

2026 年 3 月 24 日,LiteLLM 供应链攻击成了 AI 开发圈最让人头皮发麻的一次安全事件之一。这个原本用来统一路由大模型 API 调用的 Python 库,被攻击者投毒后,直接变成了信息窃取器和持久化后门的入口。最糟的是,它并不是一个冷门组件,而是大量 AI 应用、Agent 框架和自动化流水线里的基础依赖。

这篇文章根据 The CyberSec Guru 的原文进行转写整理,重点还原这条攻击链是怎么串起来的:Trivy 相关失陷如何一路传导到 LiteLLM 的发布流程,恶意版本到底做了什么,为什么 1.82.81.82.7 更危险,以及团队现在应该怎么排查和止损。

说明:本文中关于完整攻击链中间环节的细节,主要依据原始文章的技术拆解整理;关于恶意版本和项目当前公开状态,则参考 LiteLLM 官方 issue 与 PyPI 项目页。截至 2026 年 3 月 27 日,官方公开确认的恶意版本是 1.82.71.82.8,而 PyPI 项目页显示的公开最新版本为 1.82.6

这次事件到底严重在哪

LiteLLM 本来是用来解决模型接入碎片化问题的。开发者只写一套调用代码,就能在 OpenAI、Anthropic、Google Gemini 以及其他模型提供方之间切换,因此它很快成了 Python AI 工程里的基础设施级依赖。

也正因为它太基础了,LiteLLM 一旦出事,影响不会停留在单个应用层面。很多运行 LiteLLM 的环境里,本来就放着高权限的模型 API Key、数据库连接串、云平台访问令牌和 CI/CD secrets。攻击者如果能借这个包把恶意代码带进来,拿到的就不只是某个开发机,而可能是整条交付链和生产环境。

按原文给出的数据,LiteLLM 月下载量接近 9700 万次,同时又被 CrewAI、DSPy、MLflow 等 AI 框架和大量企业内部系统间接依赖。也就是说,即便你没有在 requirements.txt 里显式写 litellm,它也可能已经躺在你的依赖树里。

下面这几个点,可以先快速把这次事件的关键信息记住:

项目 信息
恶意版本 1.82.71.82.8
上传时间 1.82.72026-03-24 10:39 UTC 上传,1.82.82026-03-24 10:52 UTC 上传
最高风险点 1.82.8 通过 .pth 文件实现 Python 启动即执行
主要窃取目标 SSH 私钥、AWS/GCP/Azure 凭证、Kubernetes 配置、CI/CD 密钥、环境变量、加密钱包
持久化行为 Linux 环境下会写入 ~/.config/sysmon/sysmon.py 并注册 sysmon.service

为什么攻击者盯上了 LiteLLM

从攻击者视角看,LiteLLM 是非常理想的目标。

它不只是一个普通 Python 包,而是大量 AI 应用的“统一适配层”。很多团队为了方便切换不同模型,会把 LiteLLM 放在推理网关、Agent 编排、批处理作业和内部平台里。这样一来,只要 LiteLLM 被投毒,攻击者就有机会接触到:

  • 模型供应商的 API Key
  • 云厂商访问凭证
  • 数据库连接信息
  • Git 仓库或包仓库密钥
  • Kubernetes 配置与部署凭证
  • 自动化流水线中的发布令牌

换句话说,LiteLLM 不是因为“有漏洞”才特别危险,而是因为它天然处在高权限环境里。一旦它变成攻击入口,外溢半径会非常大。

按原文梳理的攻击链:从 Trivy 失陷到 LiteLLM 发布令牌泄露

原文最值得看的地方,不是只讲 LiteLLM 自己,而是把它放回整条供应链里看。按这篇拆解的说法,LiteLLM 并不是孤立被攻破的,它是前一个安全工具失陷之后的连锁受害者。

第一步:Trivy 相关仓库先被拿下

原文将起点追溯到 2026 年 2 月 27 日。攻击者利用 GitHub Actions 里一个长期被讨论、但现实中仍然经常被误用的风险点:pull_request_target

这个触发器和普通 pull_request 的区别在于,它是在目标仓库的上下文里执行 workflow,因此可能拿到目标仓库的高权限 secrets 或 Personal Access Token。如果工作流又把来自 fork 的不可信代码 checkout 下来并直接执行,那本质上就是把管理员权限交给了外部提交者。

按原文描述,一个名为 hackerbot-claw 的自动化账号向 Trivy 仓库提交了 Pull Request #10252。虽然 PR 很快被关闭,但对应的 workflow 已经跑起来了,攻击载荷也借这个机会把高权限令牌外传出去。随后,攻击者逐步控制了 Trivy 相关仓库和分发链路。

第二步:trivy-action 标签被重写

接下来更要命的一步,是攻击者去动了很多团队会默认信任的 trivy-action 版本标签。

原文指出,攻击者重写了 trivy-action 的 76 个版本标签中的 75 个。因为 Git 标签本身是可以被强推覆盖的,很多团队如果在 GitHub Actions 里写的是 @v1@v0.34.2 这种“可变标签”,实际上并没有锁定到不可变提交。只要标签被改写,下游 CI/CD 就会在不知情的情况下执行攻击者指定的新代码。

这也是这次事件里非常扎心的一点:被信任的安全工具,最后反过来成了供应链里的跳板。

第三步:LiteLLM 的 CI/CD 动态安装了被污染的 Trivy

原文继续追到 2026 年 3 月 24 日。LiteLLM 的维护流程里包含一个 security_scans.sh 脚本,用来在 CI/CD 中运行 Trivy 安全扫描。

问题出在这里的依赖安装方式不够严格。原文提到,LiteLLM 的流水线没有把 Trivy 锁定到不可变、可验证的安全版本,而是通过常规安装命令动态拉取。结果就是,当 Trivy 的分发链已经被污染后,LiteLLM 的 GitHub Actions runner 把恶意 Trivy 二进制拉了下来并实际执行了。

恶意代码随后在 LiteLLM 的 CI 运行环境里扫描内存和环境变量,最终找到了 PYPI_PUBLISH_PASSWORD,也就是维护者发布 LiteLLM 新版本到 PyPI 所用的关键令牌。

到这一步,桥就彻底打通了:攻击者并不需要直接入侵 LiteLLM 源码仓库,只要拿到 PyPI 发布权限,就足以把恶意版本送到全世界用户面前。

攻击者如何绕开 GitHub 仓库,直接毒化 PyPI

这次事件里一个很容易被忽视、但非常关键的点是:恶意版本并不是通过 LiteLLM 官方 GitHub 仓库里的正常提交和打 tag 流程出现的。

原文指出,攻击者是在本地构造了恶意包,然后直接用窃取到的合法发布令牌把 1.82.71.82.8 推送到了 PyPI。也就是说:

  • GitHub 仓库里不一定能看到对应的恶意提交
  • Git tag 也不一定能反映真实的恶意构建过程
  • pip 侧的常规完整性校验会通过,因为上传动作使用的是合法维护者凭证

这就是为什么很多人第一反应会误判:“仓库看着没问题,包应该也没事。” 这次事件恰恰说明,包仓库的发布链路本身就是攻击面

如果团队在那几个小时里执行了下面这类命令,就可能把恶意版本直接装进环境:

pip install --upgrade litellm

或者在镜像构建、测试流水线和 Agent 环境里以“拉最新”为默认策略安装依赖,同样会中招。

1.82.71.82.8 的区别:为什么后者更危险

虽然两个版本都带了恶意代码,但触发方式并不一样,风险等级也不一样。

版本 恶意植入位置 触发方式 风险特征
1.82.7 litellm/proxy/proxy_server.py 需要导入对应模块 更像“埋雷”,不一定安装后立刻触发
1.82.8 litellm_init.pth Python 解释器启动时自动执行 不需要显式 import litellm,攻击面显著扩大

1.82.7:代理模块注入

原文提到,1.82.7 把双层 Base64 编码的恶意载荷塞进了 litellm/proxy/proxy_server.py。这种方式的特点是,只有当运行路径触发到这个模块时,载荷才会执行。也就是说,包虽然已经被安装,但如果业务路径没走到对应模块,恶意逻辑可能暂时处于休眠状态。

1.82.8.pth 自动执行

十三分钟后,攻击者上传了 1.82.8,方式更狠。它在 site-packages 里加入了一个 litellm_init.pth 文件。

熟悉 Python 的人都知道,.pth 文件不只是拿来扩展路径的。Python 启动时会读取这些文件,而任何以 import 开头的行都会被执行。于是问题就变成了:

  • 开发者不需要显式导入 LiteLLM
  • 不需要进入特定代码路径
  • 只要该虚拟环境里启动了 Python,恶意逻辑就会跑起来

这也是为什么 1.82.8 被普遍认为危险级别明显更高。它不再依赖业务代码触发,而是把触发点前移到了 Python 启动阶段。

也是这个设计失误,让攻击被更快发现

原文还提到一个很戏剧化的细节。攻击者为了让窃密脚本在后台运行,使用了 subprocess.Popen 再起一个 Python 子进程。结果子进程启动时又会读取 .pth,于是再次启动新进程,形成递归循环,最后变成了一个意外的 fork bomb。

这直接导致很多受害机器出现下面这些异常:

  • 内存瞬间被打满
  • CPU 持续飙到 100%
  • 进程查看器打开都要等很久

也正是这种“不够安静”的异常,反而让更多研究人员和开发者更早注意到了问题。

恶意载荷具体会偷什么,又会留下什么

根据原文引用的逆向分析结果,这次载荷并不挑食,而是典型的“看到什么高价值东西就捞什么”。

首先是大范围搜集敏感信息

恶意代码会系统性搜索这些目标:

  1. 所有环境变量中的 API Key、数据库 URL 和应用密钥
  2. AWS、GCP、Azure 的默认凭证目录
  3. SSH 私钥与 known_hosts
  4. Docker registry、Kaniko 等 CI/CD 相关认证信息
  5. 本地加密钱包数据,包括 Bitcoin、Ethereum、Solana、Cardano 等

然后做加密打包并外传

原文提到,窃取到的数据会先被加密,再通过 HTTPS POST 发往攻击者的命令与控制域名 models.litellm.cloud。具体做法包括:

  • 生成 32 字节 AES-256 会话密钥
  • 用该会话密钥加密数据
  • 再用硬编码的 4096 位 RSA 公钥加密会话密钥
  • 最终打包为 tpcp.tar.gz 外传

这意味着即便团队后续抓到了可疑网络流量,也未必能直接还原已经泄露出去的内容。

如果发现是 Kubernetes 环境,还会尝试进一步接管

原文提到,恶意代码如果探测到 Kubernetes service account token,会尝试通过 Kubernetes API 在集群节点上部署高权限 Pod。也就是说,攻击者的目标并不只是偷数据,而是可能进一步拿基础设施控制权。

在 Linux 上还会留下持久化后门

针对普通 Linux 环境,恶意代码会创建:

  • ~/.config/sysmon/sysmon.py
  • sysmon.service

这个服务会定时向攻击者的控制端轮询,下载并执行后续二进制载荷。也就是说,即便你后来把 litellm 升级或卸载了,风险也不一定已经消失。

哪些团队和环境最该优先排查

如果你符合下面任意一类情况,建议把这件事按高优先级安全事件处理,而不是当成普通依赖升级事故:

  • 2026 年 3 月 24 日 恶意版本暴露窗口内执行过 pip installpip install --upgrade litellm
  • 没有直接使用 LiteLLM,但依赖了 CrewAI、DSPy、Browser-Use、Opik、Mem0 等可能间接拉到它的上层框架
  • CI/CD 流水线、Notebook、Agent runner 或 Docker 构建过程没有使用严格锁文件
  • 在这段时间内构建过镜像并推送到了镜像仓库
  • 开发环境或 runner 中保存着模型 API Key、云凭证、SSH key、数据库密码等高价值秘密

这里最容易被低估的一点是传递依赖。很多团队会下意识地说“我们没用 LiteLLM”,但只要你的上层框架在解析依赖时自动拉到了这两个版本,风险并不会因为你没直接 import litellm 就自动消失。

现在该怎么止损:重点不是卸载,而是隔离、重建和轮换密钥

原文给出的处置思路很明确:如果受影响环境真的跑过恶意版本,就应该先假定已经失陷,而不是抱着侥幸心理觉得“可能没执行到”。

1. 先隔离

立刻隔离以下对象:

  • 开发者工作站
  • CI/CD runner
  • 在暴露窗口里构建过的容器镜像
  • 运行过受影响虚拟环境的测试机或生产容器

2. 检查是否装过受影响版本

可以先做最直接的版本确认:

pip show litellm
python -m pip freeze | grep -i '^litellm=='

如果返回的是 1.82.71.82.8,不要把它当成“只是升级一下就好”的问题。

3. 查找持久化痕迹

重点检查下面这些路径和服务:

find "$HOME/.config" -path '*/sysmon/sysmon.py' 2>/dev/null
systemctl --user list-unit-files | grep -i sysmon

如果是在容器、共享 runner 或 root 环境里排查,也要同步检查对应用户目录和系统服务配置。

4. 直接重建环境,不要原地修补

更稳妥的做法通常是:

  • 删除受影响虚拟环境
  • 清掉镜像层缓存或重新构建镜像
  • 从可信基础环境重新安装依赖
  • 重新注入已经轮换过的新密钥

只做 pip uninstall litellm 没什么意义,因为恶意逻辑有可能早就执行完了,甚至已经留下持久化后门。

5. 轮换一切可能被看到的秘密

只要机器上跑过受影响版本,就应该默认下面这些信息都可能已经泄露:

  • AWS IAM key、GCP service account、Azure service principal
  • GitHub PAT、GitLab token、Docker registry 凭证
  • OpenAI、Anthropic 等模型 API Key
  • 数据库密码和第三方服务 Token
  • SSH 私钥和对应服务器上的公钥信任关系
  • Kubernetes kubeconfig、Secret 和部署凭证

这一步常常最痛,但也是最不能省的一步。

这次事件真正暴露出的 3 个供应链问题

如果只把这次攻击理解成“LiteLLM 一次倒霉的投毒事件”,那收获其实不大。它真正暴露出来的是现代 AI 开发里三件已经不能再拖的事情。

1. 锁文件不是可选项

没有锁文件、默认拉最新版本、CI/CD 每次动态解析依赖,这些在普通业务里已经够危险了,在 AI 工程里只会更危险。因为 AI 环境里通常同时保存着更多高价值秘密,一次依赖投毒带来的损失也会更大。

2. Git 标签不是安全锚点

这次事件再次说明,@v1@latest@stable 这种可变引用不适合作为安全边界。对 GitHub Actions、第三方脚本和二进制工具来说,真正稳妥的方式应该是固定到不可变 commit SHA,而不是信任一个随时可能被改写的标签。

3. 自动化系统必须最小权限

如果 CI/CD token 拥有“发布新版本”“删除 release”“修改仓库配置”等过大的权限,任何一次 runner 失陷都会把这些能力一并送给攻击者。自动化系统的密钥应该:

  • 权限尽量收窄
  • 生命周期尽量缩短
  • 使用范围尽量隔离
  • 被持续审计和轮换

这不是额外的工程负担,而是开源供应链时代的基本卫生习惯。

FAQ:几个最常见的问题

1. 我没有直接用 LiteLLM,也需要查吗?

需要。最危险的地方本来就是传递依赖。如果你的上层框架在暴露窗口内自动解析到了 1.82.71.82.8,那风险和直接安装并没有本质区别。

2. 现在还能用 LiteLLM 吗?

原文给出的说法是,恶意版本删除后,1.82.9 及以上被视为安全版本。但截至 2026 年 3 月 27 日,PyPI 项目页显示的公开最新版本是 1.82.6。如果你在其他地方看到“已经恢复到 1.82.9”之类的说法,建议以当时的官方 issue 和 PyPI 页面为准,不要只凭二手文章判断。

3. 为什么传统杀毒或静态扫描没有第一时间拦住它?

一个原因是恶意载荷做了混淆,比如双层 Base64 编码;另一个原因是它大量使用的是 Python 环境里常见、看起来并不突兀的标准行为,例如 subprocess、文件遍历和 HTTPS 请求。这类攻击如果再叠加合法签名发布,很容易绕过基于静态特征的浅层检测。

4. 如果我们的 CI 里也用了 Trivy,要不要一起当成事件处理?

如果你们依赖的是未固定提交哈希的 trivy-action,或者在那段时间里动态下载过 Trivy,确实应该把它纳入同一轮审计,重点排查 runner 日志、Secrets 使用记录和令牌轮换情况。

参考链接

关于

关注我获取更多资讯

月球基地博客公众号二维码,扫码关注获取更多 AI 与编程资讯
📢 公众号
月球基地博客作者个人微信二维码,扫码交流 AI 与编程话题
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计