LiteLLM 再度被投毒:PyPI 上的 1.107.0 偷走了所有能偷的凭据

LiteLLM 用户在 10 月 21 日晚打开包管理器时,装到的是一个经过精心伪装的木马。

BerriAI 旗下的 LiteLLM 是 Python 生态里使用最广的 AI 网关之一,日均下载量数百万次,作用是把 OpenAI、Anthropic、Cohere 等上百家模型服务商的 API 收敛成一套统一接口。一个 AI 应用要同时对接多家模型、做负载均衡和成本追踪时,LiteLLM 通常是中间那层。正因为位置关键,它一旦出事,波及面远超一个普通库。

10 月 21 日 11:39 UTC,恶意版本 1.107.0 被上传到 PyPI。Wiz Research 在两小时内完成分析并发布报告,PyPI 于 13:20 UTC 隔离了该版本,GHCR 和 Docker Hub 上的 v1.107.0、latest、main-latest、main-stable 镜像在 13:53 UTC 左右移除。官方确认的最后一个安全版本是 v1.106.2。

Wiz Research 发布的 LiteLLM 投毒事件通告图

装上即运行:不需要 import 的持久化

这次投毒的技术核心在于三个文件的落点。恶意 wheel 解包后,cook.pth、cook.py 和 sitecustomize.py 三个文件被直接放进 site-packages 根目录,而不是包自己的子目录里。

Python 解释器每次启动时,会自动扫描 site-packages 下的 .pth 文件并执行其中以 import 开头的行,同时尝试加载 sitecustomize 模块。这意味着只要机器上装有这个恶意包,之后启动任何 Python 进程(不管和 LiteLLM 有没有关系)都会触发恶意代码。Wiz 指出,.pth 与 sitecustomize.py 两种持久化机制同时使用的组合,在公开记录里是第一次出现。

两条链路各有分工:

  • cook.py 负责一次性窃密。自解混淆后运行窃密例程,在 /tmp/.pc_once_72129118c59f5636c0bb 写入运行标记防止重复执行(这个标记路径在 Windows 上通常不存在,导致 Windows 侧窃密流程直接失败)。
  • sitecustomize.py 负责长期后门。仅在 Linux 和 macOS 上生效,周期性从一个 GitHub 死投仓库(letters9813/pythonclient-test)拉取 domain.txt,拿到域名后再下载第二阶段 Python 脚本 http://<domain>/codepy 执行。该文件除初始测试值外从未写入真实域名,仓库随后被 GitHub 下架。

窃密模块针对的正是 AI 开发者的全套家当

内嵌的窃密器内部代号 privcollect,包含 16 个模块,覆盖浏览器、云厂商和 AI 服务:

  • 浏览器凭据与 Cookie:Chrome、Firefox、Edge、Safari 四家全收。
  • AWS:~/.aws/ 目录、环境变量、EC2/ECS 实例元数据(IMDS)三个来源并行。
  • GCP:服务账号密钥与应用默认凭据;Azure:CLI 缓存令牌。
  • SSH 私钥、GPG 密钥、shell 历史文件。
  • AI 专属目标:环境变量和配置文件里的 OpenAI、Anthropic、Cohere 等 LLM 服务商 API key。

对 LiteLLM 自身部署最致命的一条:LiteLLM Proxy 把上游服务商的 API key 明文存在 PostgreSQL 里,窃密器直接执行 SELECT * FROM "LiteLLM_CredentialsTable" LIMIT 500,把网关配置的全部模型凭据一锅端。

窃取的数据用 AES-256-CBC 加密(密钥再用 RSA-4096 公钥封装),通过 HTTP POST 发往 www.litellm[.]dev/healthcheck。这是一个攻击者注册的仿冒域名,请求还带硬编码的 X-Token 头和 Internet Explorer 7 的 User-Agent。整个外传通道伪装成健康检查流量。

Wiz Research 绘制的 payload 执行流程图

攻击者怎么打进发布线的

BerriAI 在事件声明中说明了入侵路径,这条链路比 payload 本身更值得开发者警惕。

恶意操作通过三名工程师的 GitHub 账号完成,凭据是被窃的 GitHub CLI OAuth token。攻击者用一个账号触发 project-releaser 工作流,再用另一个账号批准它,把双人复核机制变成了攻击者的独角戏。

token 的泄露源头出人意料:BerriAI 调查发现,特定情况下 Claude Code 会把用户的 GitHub CLI OAuth token 嵌进响应体,而这些响应体被日志记录后,token 就出现在了公司内部 LiteLLM 部署的日志里。这个内部部署暴露在公网上,攻击者找到了一个名字不起眼的异常账号登入,从日志里捞走 token,再用它攻陷发布流水线。BerriAI 尚未确认攻击者是如何进入内部部署的,初步怀疑是利用了一个近期已修补的漏洞。

所有涉事 token 已吊销。

Wiz 还注意到 payload 的开发过程有明显 LLM 辅助痕迹:冗长的变量名(如 encrypted_private_key_output)、过度防御的通用 try/except、在未测试平台上静默失败的分支(Windows 标记路径不存在、fcntl/fork 调用让远程加载器崩溃)、定义了却从未被调用的死代码模块。功能在主干路径上可用,边缘处脆弱,符合迭代式提示词开发的特征。

已观测到的后续利用

窃密不是终点。Wiz 观测到数据外传约 4.5 小时后(16:20 至 17:01 UTC),攻击者开始验证偷到的 AWS 凭据:来自 168.100.8[.]57 和 165.227.201[.]8 两个 IP 的请求对失陷 CI runner 和 Kubernetes 节点的实例角色凭据逐一调用 sts:GetCallerIdentity,随后是 bedrock:ListFoundationModels 和 ListModels。这组调用的意图指向 LLMjacking:盗用云账号额度跑大模型推理,让失窃企业替攻击者的 token 账单买单。部分环境里攻击者还枚举了 IAM 角色与访问密钥,做了常规云资源探测。

一年内的第二起,入口完全不同

把时间线拉长看,这是 LiteLLM 一年内第二次被投毒,而两次的入口没有一处相同。

2026 年 3 月 24 日的第一起事故中,v1.82.7 和 v1.82.8 被植入恶意代码,恶意版本上线约 40 分钟后被隔离。Trend Micro 的复盘把那次攻击归因于犯罪组织 TeamPCP 的多生态供应链战役:攻击者先在 2 月利用 Trivy 安全扫描器 CI 的一次 pull_request_target 配置失误偷走 aqua-bot 的访问令牌,3 月又用未失效的旧令牌强行覆盖 trivy-action 的 76 个 release tag。被污染的扫描器随后进入 LiteLLM 的 CI 环境(当时跑在 CircleCI 上,所有步骤共享环境,发布凭据以静态环境变量形式常驻),窃取 PyPI、GHCR、Docker 的发布凭据并完成投毒。CrowdStrike 的分析指出,真正的扫描任务在恶意代码运行后照常执行、输出正常,流水线上看不出任何异样。

那之后 LiteLLM 做了系统性整改:CI 按单元测试、集成测试、安全扫描、发布四个环节拆成隔离环境;发布凭据转向 PyPI Trusted Publisher 和 GHCR 令牌认证等短期凭据;全部 GitHub Actions 固定到具体 SHA;删除约 6000 个遗留分支并加自动清理策略;从 v1.83.0-nightly 起所有 Docker 镜像用 Cosign 签名,用户可以凭固定 commit hash 上的公钥独立验证镜像来源;并请 Mandiant 做取证、与白帽团队 Veria Labs 复核代码库。

3 月整改的思路是收紧 CI/CD 自身:环境隔离、凭据短期化、发布可验证。这次攻击绕过了所有这些防线,因为它根本没碰 CI/CD 的凭据仓库,而是从日志里的 OAuth token 和一个暴露在公网的内部服务进来。供应链防御的下一个课题由此变得具体:发布安全的边界不止在流水线配置里,还在公司自己运维的每一个服务和每一行日志里。Claude Code 等编码代理把高权限令牌写进响应体的行为,以及这些响应体被下游日志系统记录的链路,在 10 月 21 日之前很少被当作攻击面来审计。

受影响范围与排查清单

结合 Wiz 与 BerriAI 的披露,受影响的是 PyPI 上的 litellm 1.107.0,以及 GHCR 和 Docker Hub 上带恶意层的 v1.107.0、latest、main-latest、main-stable 镜像。两个仓库的恶意版本已被移除或隔离。

如果在 10 月 21 日 11:39 UTC 之后装过或拉过上述版本,按这个顺序排查:

  1. 立即固定到 v1.106.2 或官方之后发布的修复版本,重建镜像。
  2. 轮换机器上可能被读取的全部凭据:LLM 服务商 API key(OpenAI/Anthropic/Cohere 等)、~/.aws/、GCP 服务账号、Azure CLI 缓存、SSH 私钥,以及浏览器保存的密码与 Cookie。
  3. 用 LiteLLM Proxy 的部署要检查数据库:SELECT * FROM "LiteLLM_CredentialsTable" 确认凭据表是否被读取过(结合数据库审计日志),并轮换其中全部上游 key。
  4. 检查 /tmp/.pc_once_72129118c59f5636c0bb 标记文件与 PRIVCOLLECT_ACTIVE=1 环境变量,命中即确认 payload 在本机运行过。
  5. 检查云审计日志中 10 月 21 日 16:20 UTC 之后的 sts:GetCallerIdentity、bedrock:ListFoundationModels 调用,来源 IP 对照 168.100.8[.]57 和 165.227.201[.]8;再查 sitecustomize.py 是否从 letters9813/pythonclient-test 拉取过 domain.txt。
  6. 维护自己的 AI 网关或内部服务时,审查日志系统是否会记录完整响应体,并对编码代理类工具输出的令牌做脱敏;公网可达的管理面一律收进内网或加认证。

LiteLLM 官方声明、Wiz 的完整 IOC 表(含 6 个恶意 wheel 与镜像的 SHA256、外传端点、硬编码 X-Token)都可以在原文核对。对于把 AI 网关当作核心基础设施的团队,这份 IOC 表值得直接导入威胁情报平台跑一遍存量资产。

来源: