LiteLLM 1.107.0 遭植入后门窃取凭证

LiteLLM 1.107.0遭植入后门窃取凭证。该恶意版本覆盖三大平台,含三个自动运行文件,可收集SSH密钥、云凭证及AI令牌等敏感信息并加密外传。目前PyPI已删除该版本,上游源码未受影响。

LiteLLM 1.107.0 版本在 PyPI 上发布时,被植入了凭证窃取器和远程代码执行后门。该恶意版本覆盖了 Linux、macOS 和 Windows 平台,所有七个轮子(wheels)均包含三个植入文件。这些文件会在主机上的任何 Python 进程启动时自动运行。值得注意的是,上游 GitHub 源代码中并不存在这些恶意文件。目前,PyPI 已删除了该版本。

三个植入文件随Python启动自动运行

经核查,版本 1.106.2、1.107.0rc1 和 1.107.0rc2 是干净的。其依赖的伴侣包 litellm-proxy-extras==0.4.110 和 litellm-enterprise==0.1.78 同样未被篡改。

攻击机制分析

安装 litellm==1.107.0 会在 site-packages 目录中放置 3 个文件。Python 在每次启动时都会加载其中的两个:

  1. cook.pth:导入 cook.py,这是一个身份窃取器。它负责收集 SSH 密钥、云凭证、CI/CD 秘密、AI 工具令牌、浏览器密码以及存储在 LiteLLM 代理数据库中的 LLM 提供商密钥。数据经过加密后发送至 www.litellm[.]dev。
  2. sitecustomize.py:启动一个后台守护进程。该进程每小时从 GitHub 文件中读取服务器地址,以维持远程控制通道。

恶意代码如何进入发行版

在 1.107.0 的源分布(sdist)中,pyproject.toml 文件的构建部分增加了一行配置:

[tool.maturin]
data-dir = "cook"

设置 maturin data-dir="cook" 会将 cook/ 目录下的文件复制到 wheel 包的 litellm-1.107.0.data/purelib/ 中。pip 在安装时会将这些文件部署到 site-packages。

这是唯一可见的配置变更。在 BerriAI/litellm 仓库中,没有任何公开的 commit 包含 data-dir = "cook" 或 cook/ 文件。

Wheel 包的 RECORD 文件列出了三个新增文件:

../../../site-packages/cook.pth,sha256=...
../../../site-packages/cook.py,sha256=...
../../../site-packages/sitecustomize.py,sha256=...

发行版与标签不匹配

标签 v1.107.0 指向提交 66c8f5b (UTC 06:59)。然而,该提交的 pyproject.toml 显示版本号为 "1.108.0"。SafeDep 将每个 sdist 文件与 Git 树进行了比对,发现恶意 sdist 实际上匹配的是后续的主要提交 a8113b0 (UTC 08:50),外加五个编辑过的文件和 cook/ 目录。

其中四个编辑过的文件是 Cargo.toml,增加了 readme="README.md" 行。合法的 1.106.2 和 rc2 版本也有相同的编辑。第五个编辑过的文件是 pyproject.toml,与 a8113b0 相比:

--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [build-system]
 requires = ["maturin>=1.0,<2.0"]
 build-backend = "maturin"
 
 [project]
-name = "litellm"
-version = "1.108.0"
+name = "litellm"
+version = "1.107.0"

此编辑将版本号改为 1.107.0,并将伴侣包固定为 rc2 值。因此,针对 rc2 的依赖性差异看起来并无异常。合法的 1.106.2 sdist 与其标签提交 49943cd 完全匹配,除了 Cargo.toml 中的 readme 行。

Wheel 包的构建痕迹

恶意 Wheel 包与正常的 BerriAI 发布管道输出特征一致:

  • WHEEL 文件名由 maturin 1.9.4 生成,与合法版本相同。植入文件拥有正确的 RECORD 条目,未显示构建后的编辑痕迹。
  • Zip 时间戳显示独立的原生构建过程:macOS 在 UTC 11:20,Linux 在 11:21,Windows 在 11:37。
  • 在 Windows wheel 包中,全部 2,804 个 .py 文件均采用 CRLF 行尾格式,包括三个植入文件。Linux wheel 包则无此现象。这种模式通常出现在启用了行尾转换的 Git checkout 环境中。

没有公开的 GitHub Actions 运行记录用于构建或发布该版本。安全团队检查了 UTC 9:30 至 12:30 之间的所有 539 次运行,均未发现相关活动。在 PyPI 上传后约 10 分钟(UTC 11:50),litellm-dispatcher[bot] 发布了 GitHub 版本 v1.107.0。同样的延迟也发生在 1.106.2 版本上。PyPI 上没有 1.107.0、1.106.2 或 rc2 的来源证明。

据 SafeDep 估计,BerriAI 的私有发布管道是从攻击者修改后的 Git 源中构建了这些 Wheel 包。攻击者可能使用了受损的维护者或机器人凭证、私有分支或镜像。使用窃取的 PyPI 令牌从攻击者自己的机器直接上传不太符合现有证据。具体原因需通过 BerriAI 的内部管道日志才能证实。

负载 1:cook.py 中的凭证窃取器

Python 在启动时会执行 site-packages 中每个以 import 开头的 .pth 文件。cook.pth 包含 import cook,导致窃取器在主机上的每个 Python 进程中运行,包括应用程序、pip、IDE 语言服务器和 CI 作业。

cook.py 将大约 61KB 的 Python 代码包裹在 exec(zlib.decompress(base64.b64decode('...'))) 层中。解码后的代码以如下配置开头:

# Decoded configuration snippet
CONFIG = {
    "endpoint": "https://www.litellm.dev/healthcheck",
    "token": "XWiA8gZNa7NcX2XRoAEb"
}

收集的数据范围

模块目标数据
网络信息主机名、用户、操作系统、IP 地址、网络设置
浏览器_pwChromium 保存的密码(使用个人资料主密钥解密,Windows 上使用 DPAPI)、浏览器 logins.json 和 key4.db
浏览器历史浏览器历史记录
ssh_keys~/.ssh/* 目录下的所有文件
终端记录bash 和 zsh 历史记录、PowerShell PSReadLine、cmd 历史记录
云_creds~/.aws/credentials 和 config、Azure 配置文件和令牌、gcloud 凭证、~/.kube/config、~/.docker/config.json、gh hosts.yml、rclone.conf、~/.netrc
开发秘密$HOME 下的 .pem, .key, .pfx, .p12, .pk8, .ppk 文件、Composer auth.json、FileZilla 保存的服务器信息
容器_信贷Kubernetes 服务账户令牌
DevOps 秘密GitLab Runner config.toml、Jenkins 初始管理员密码和 master.key、.vault-token、Terraform credentials.tfrc.json、Wrapper 设置
AI 机器人Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, VS Code mcp.json, Gemini CLI, Codex auth.json, Continue, Aider, Goose, Open Source, Qwen, Smash, Kiro, Zed, Hugging Face 令牌
在线服务自身环境及通过 /proc/<pid>/environ 获取的其他进程的环境变量
db_litellm读取 LiteLLM_CredentialsTable 和 LiteLLM_ProxyModelTable 表,其中包含 LiteLLM 代理的 LLM 提供商 API 密钥
云_元数据AWS 实例元数据服务 (IMDSv2, 然后 v1):实例身份和角色凭证
GPG/TLS 密钥GPG 密钥、TLS 私钥

数据传输方式

  1. 使用随机 AES-256-CBC 密钥加密 JSON 结果,并使用 RSA-OAEP 和嵌入式公钥加密该 AES 密钥。
  2. 将结果打包成 tar.gz (hdr.json, data.bin, key.bin),并以 POST 请求发送到 www.litellm[.]dev/healthcheck,标题中包含 X-Token:XWiA8gZNa7NcX2XRoAEb。
  3. 成功上传后,写出标记文件 /tmp/.pc_once_72129118c59f5636c0bb。同时设置环境变量 PRIVCOLLECT_ACTIVE=1,以避免在同一进程树中重复运行。

负载 2:sitecustomize.py 中的后门

Python 在启动时会导入 sitecustomize,因此该文件也在每个 Python 进程中运行。

import os
import sys
import subprocess

# Simplified logic representation
def daemon():
    # Locking mechanism and fork logic implied in original code structure
    pass
  1. 对 /tmp/remote_code_loader.lock 获取独占锁,确保每台主机只运行一个守护进程。
  2. 调用 fork() 和 setsid(),将标准输入输出重定向到 /dev/null,并更改工作目录到 /,从而脱离父进程。
  3. 每小时执行以下操作:
    1. 从攻击者的 GitHub 仓库读取 domain.txt,提取第一行看似域名的内容。
    2. 运行其内置的窃取器副本(该副本没有“每主机一次”的标记限制)。
    3. 下载 http://<domain>/codepy 并使用 exec() 执行。

攻击者可以通过编辑 domain.txt 将守护进程指向新的服务器。每台受感染的主机都会运行该服务器返回的 codepy 内容。

在 UTC 13:40,GitHub 账户 letters9813 及其仓库返回 404 错误,此时距离版本发布仅过去约两小时。后门因此无法找到服务器地址而失效。但在删除之前运行过守护进程的主机,可能已经接收并执行了第二阶段代码。SafeDep 未能恢复具体的服务器地址。

事件时间线

所有时间均为 2026-10-21 UTC。

时间事件
06:59创建标签 v1.107.0 提交 66c8f5b
08:50在 main 分支上产生提交 a8113b0,作为发布源的基线
10:44合法的 1.106.2 版本上传至 PyPI
11:20 - 11:37构建恶意的 1.107.0 Wheel 包
11:39 - 11:401.107.0 上传至 PyPI(共 8 个文件)
11:50litellm-dispatcher[bot] 发布 GitHub 版本 v1.107.0
13:40观察结果:1.107.0 已从 PyPI 移除,letters9813 账户已被删除

妥协指标 (IoC)

网络指标

指标用途
www.litellm[.]dev, hxxp://www.litellm[.]dev/healthcheck接收窃取的数据
HTTP Header X-Token: XWiA8gZNa7NcX2XRoAEb随每次上传发送
raw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt后门在此读取服务器地址(账户已删除)
hxxp://<domain>/codepy第二阶段代码下载
来自 Python 进程的 169.254.169.254 请求窃取 AWS 实例凭证

主机指标

指标含义
site-packages 中的 cook.pth, cook.py, sitecustomize.py植入物已安装
/tmp/.pc_once_72129118c59f5636c0bb窃取器已上传数据
/tmp/remote_code_loader.lock后门守护进程已运行
环境变量 PRIVCOLLECT_ACTIVE=1存在于受感染的进程中
分离的 Python 守护进程(父 PID 为 1,独立会话,工作目录为 /)后门循环

文件哈希 (SHA-256)

文件SHA-256

评论 0

0/500

评论需审核后展示,请文明发言

💬
还没有评论,来说两句

相关阅读