PyPI 上的 LiteLLM 1.107.0 遭投毒并包含凭据窃取器与后门
Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor
PyPI 发布的 LiteLLM 1.107.0 版本包含凭据窃取器与远程代码执行后门,安装后会在每次启动 Python 时自动执行。该恶意负载会搜集 SSH 密钥、云服务凭据、AI 工具凭据及 LiteLLM 代理数据库中的 API 密钥并加密外传,同时通过后台守护进程定期下载并运行远程代码。目前 PyPI 已移除该版本,受影响版本仅限 1.107.0。
文章详细披露了 PyPI 官方库被投毒事件的技术细节与影响范围,可为开发者排查凭据泄露风险和供应链安全审计提供事实参考。
• 2026年10月21日 • 阅读时间 6 分钟
PyPI 上的 LiteLLM 1.107.0 版本包含一个凭据窃取程序和一个远程代码执行后门。所有七个 wheel 安装包(Linux、macOS、Windows)都会将它们作为三个文件安装在 site-packages 中。无论主机上启动哪个 Python 进程(甚至是从未导入 litellm 的进程),这两个程序都会在进程启动时运行。上游 GitHub 源码中并不包含这些文件。此后,PyPI 已经删除了该版本。
版本 1.106.2、1.107.0rc1 和 1.107.0rc2 是干净的。1.107.0 锁定的配套软件包 litellm-proxy-extras==0.4.110 和 litellm-enterprise==0.1.78 也是干净的。
攻击的工作原理
安装 litellm==1.107.0 会在 site-packages 中放置三个文件。Python 每次启动时都会加载其中两个。
cook.pth会导入cook.py(一个凭据窃取程序)。它会收集 SSH 密钥、云凭据、CI/CD 密钥、AI 工具令牌、浏览器密码以及 LiteLLM 代理数据库中的 LLM 提供商密钥。它会对数据进行加密并发送到www.litellm[.]dev。sitecustomize.py会启动一个后台守护进程。每隔一小时,该守护进程就会从一个 GitHub 文件中读取服务器地址,再次运行窃取程序,并从该服务器下载并运行代码。
植入物是如何进入发行版的
1.107.0 源码分发包(sdist)中的 pyproject.toml 在 maturin 构建部分多出了一行:
pyproject.toml (litellm-1.107.0.tar.gz)
TOML 6 lines
maturin data = "cook" 设置将 cook/ 文件夹复制到 wheel 中的 litellm-1.107.0.data/purelib/。pip 会将该文件夹安装到 site-packages 中。
sdist 中漏掉了 cook/ 文件夹,因此这一行是唯一可见的更改。在 BerriAI/litellm 的任何公开提交(commit)中都不包含 data = "cook" 或 cook/ 文件夹。
wheel 的 RECORD 列出了三个新增的文件:
litellm-1.107.0.dist-info/RECORD
TEXT 4 lines
版本与标签不符
标签 v1.107.0 指向提交 66c8f5b(UTC 06:59)。其 pyproject.toml 显示为 version = "1.108.0"。SafeDep 通过 blob 哈希将每个 sdist 文件与 git 树进行了比较。sdist 与稍后的 main 提交 a8113b0(UTC 08:50)相匹配,外加五个被编辑的文件和 cook/ 文件夹。
被编辑的文件中有四个是 Cargo.toml 文件,它们增加了一行 readme = "README.md"。合法的 1.106.2 和 rc2 版本也有同样的编辑。第五个是 pyproject.toml,此处将其与 a8113b0 进行比较:
pyproject.toml (a8113b0 → litellm-1.107.0.tar.gz)
DIFF 11 lines
该编辑将版本改回 1.107.0,并将配套软件包锁定到 rc2 的值。因此,与 rc2 的依赖项差异对比显示不出任何异常。合法的 1.106.2 sdist 与其标签提交 49943cd 相匹配,除了 Cargo.toml readme 行之外。
wheel 是如何构建的
恶意 wheel 与正常的 BerriAI 发布流水线的输出相符:
WHEEL文件标明使用的是maturin1.9.4,与合法版本中的版本相同。植入物文件具有正确的RECORD条目,这表明构建后没有进行任何编辑。- Zip 时间戳显示了各自独立的本地构建:macOS 为 UTC 11:20,Linux 为 11:21,Windows 为 11:37。
- 在 Windows wheel 中,所有 2,804 个
.py文件的行尾均为 CRLF,包括三个植入物文件。Linux wheel 则没有。在 Windows 上进行带行尾转换的 git checkout 操作会产生这种模式。
没有任何公开的 GitHub Actions 运行记录构建或发布过该版本。SafeDep 检查了 UTC 09:30 到 12:30 之间的全部 539 次运行。在 UTC 11:50(即上传到 PyPI 大约 10 分钟后),litellm-dispatcher[bot] 发布了 GitHub 版本 v1.107.0。1.106.2 版本也出现了同样的延迟。PyPI 没有针对 1.107.0、1.106.2 或 rc2 的来源证明(provenance attestations)。
SafeDep 以中到高的置信度评估认为,BerriAI 的私有发布流水线是用攻击者修改过的 git 源码构建了这些 wheel 包。攻击者可能使用了被盗的维护者或机器人凭据、私有分支或镜像,或者流水线构建时所用的引用。从攻击者的机器上使用被盗的 PyPI 令牌这一情况与证据的吻合度较低。只有 BerriAI 的流水线日志才能确认原因。
Payload 1:cook.py 中的凭据窃取器
在启动时,Python 会运行 site-packages 中 .pth 文件内以 import 开头的每一行。由于 cook.pth 包含 import cook,因此该窃取器会在主机上的每个 Python 进程中运行:包括应用程序、pip、IDE 语言服务器以及 CI 作业。
cook.py 将大约 61 KB 的 Python 代码包裹在了一层 exec(zlib.decompress(base64.b64decode('...'))) 中。解码后的代码以以下配置开始:
cook.py (解码后)
PYTHON 10 行
它收集的内容
| 模块 | 目标 |
|---|---|
sysinfo, netinfo | 主机名、用户、操作系统、IP 地址、网络设置 |
browser_pw | Chromium 保存的密码,使用配置文件主密钥(Windows 上的 DPAPI)解密。Firefox logins.json 和 key4.db |
browser_hist | 浏览器历史记录 |
ssh_keys | ~/.ssh/* |
shell_hist | bash 和 zsh 历史记录、PowerShell PSReadLine、cmd 历史记录 |
cloud_creds | ~/.aws/credentials 和 config、Azure 配置文件和令牌、gcloud 凭据、~/.kube/config、~/.docker/config.json、gh hosts.yml、rclone.conf、~/.netrc |
dev_secrets | $HOME 下的 .pem、.key、.pfx、.p12、.pk8、.ppk 文件、Composer auth.json、FileZilla 保存的服务器 |
container_creds | Kubernetes 服务账户令牌 |
cicd_secrets | GitLab runner config.toml、Jenkins initialAdminPassword 和 master.key、.vault-token、Terraform credentials.tfrc.json、Packer 设置 |
ai_devtools | Claude Code、Claude Desktop、Cursor、Windsurf、Copilot、VS Code mcp.json、Gemini CLI、Codex auth.json、Continue、Aider、Goose、opencode、Qwen、Crush、Kiro、Zed、Hugging Face 令牌 |
env_vars | 其自身的环劣以及通过 /proc/<pid>/environ 获取的其他进程的环境 |
db_litellm | 读取 DATABASE_URL 并转储保存了 LiteLLM 代理的 LLM 提供商 API 密钥的 LiteLLM_CredentialsTable 和 LiteLLM_ProxyModelTable 表 |
cloud_metadata | AWS 实例元数据服务(IMDSv2,然后是 v1):实例身份和角色凭据 |
gpg_keys, tls_privkey | GPG 密钥、TLS 私钥 |
它如何发送数据
- 它使用随机的 AES-256-CBC 密钥加密 JSON 结果,并用 RSA-OAEP 以及嵌入的公钥加密该密钥。
- 它将结果打包成 tar.gz(
hdr.json、data.bin、key.bin),并通过带有X-Token: XWiA8gZNa7NcX2XRoAEb标头的 POST 请求将其发送到www.litellm[.]dev/healthcheck。 - 成功上传后,它会写入
/tmp/.pc_once_72129118c59f5636c0bb,以便每个主机只收集一次。它还会设置PRIVCOLLECT_ACTIVE=1,以避免在同一个进程树中再次运行。
Payload 2:sitecustomize.py 中的后门
Python 在启动时会导入 sitecustomize,因此该文件也会在每个 Python 进程中运行。
sitecustomize.py
PYTHON 5 行
- 它对
/tmp/remote_code_loader.lock获取排他锁,因此每个主机只运行一个守护进程。 - 它调用
fork()和setsid(),将标准输入和输出发送到/dev/null,并更改目录到/以从父进程分离。 - Every hour, it:
- 从攻击者的 GitHub 仓库读取
domain.txt并获取第一行看起来像域名的内容, - 运行其嵌入的、没有“每个主机一次”标记的窃取器副本,
- 下载
http://<domain>/codepy并用exec()运行它。
- 从攻击者的 GitHub 仓库读取
攻击者可以通过编辑 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:40 | 1.107.0 已上传至 PyPI(共 8 个文件) |
| 11:50 | litellm-dispatcher[bot] 发布 GitHub 版本 v1.107.0 |
| 13:40 | 观察结果:1.107.0 从 PyPI 消失,letters9813 账号被删除 |
入侵指标
网络
| 指标 | 用途 |
|---|---|
www.litellm[.]dev, hxxp://www.litellm[.]dev/healthcheck | 接收被盗数据 |
HTTP 头 X-Token: XWiA8gZNa7NcX2XRoAEb | 随每次上传发送 |
raw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt | 后门在此处读取其服务器地址(账号已删除) |
hxxp://<domain>/codepy | 第二阶段代码下载 |
来自 Python 进程对 169.254.169.254 的请求 | 窃取 AWS 实例凭据 |
主机
| 指标 | 含义 |
|---|---|
cook.pth, cook.py, sitecustomize.py 位于 site-packages | 已安装植入程序 |
/tmp/.pc_once_72129118c59f5636c0bb | 窃密器已上传数据 |
/tmp/remote_code_loader.lock | 后门守护进程已运行 |
环境变量 PRIVCOLLECT_ACTIVE=1 | 存在于受感染的进程中 |
分离的 Python 守护进程(父 PID 1,自有会话,工作目录 /) | 后门循环 |
文件哈希 (SHA-256)
| 文件 | SHA-256 |
|---|---|
litellm-1.107.0.tar.gz | 2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819 |
litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl | 462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7 |
litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl | 668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6 |
litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl | 1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00 |
litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl | c2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054 |
litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl | 7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d |
litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl | 0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a |
litellm-1.107.0-cp310-abi3-win_amd64.whl | 12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484 |
植入的文件,来自 wheel 包 RECORD:
| 文件 | SHA-256 (URL-safe base64) |
|---|---|
cook.pth | fxkWMucyBwXRVLhSBPGuVGtUOsvX6IYSKbdvUEK5K9s |
cook.py | ndCjkm-Wo364bPHuIxDZvpMTnMTr7z754u_pUxOQviU |
sitecustomize.py | NL7wT-iKydCq4EdWMLFzhoHRW5TyM6yKpG6s9ZaDBfo |
- pypi
- python
- malware
- supply-chain
- litellm
作者
来自 SafeDep 博客 的最新动态
关注开源安全与工程的最新更新和见解
发布代码。
而非恶意软件。
在您的机器上使用开源工具免费开始。扩展至面向您组织的统一平台。
来源:Hacker News-首页 · safedep.io