3月24日,Python生态中一个广泛使用的AI集成库LiteLLM遭遇了严重的供应链攻击,其PyPI上的1.82.7和1.82.8版本被植入了恶意代码。这次攻击影响面巨大,因为该库的日均下载量超过300万次。恶意版本在官方仓库中存留了约三个小时后才被下架隔离。
值得注意的是,攻击者并非直接攻陷了LiteLLM维护者的账户,而是采用了一种更隐蔽的“曲线救国”方式。他们首先瞄准了开源安全扫描工具Trivy,并利用其CI/CD流程中的漏洞,最终窃取了LiteLLM发布PyPI包的凭证,从而完成了这次投毒。这表明,现代云原生/IaaS环境下的依赖链条异常复杂,任何一环的疏漏都可能被攻击者利用。
攻击链条是如何铺开的?
整个攻击的时间线清晰地展示了攻击者有组织、分步骤的渗透过程:
- 2月底:攻击者利用了Trivy项目GitHub Actions工作流中
pull_request_target 事件的设计风险,成功窃取了项目自动化账户 aqua-bot 的凭证。
- 3月19日:攻击者将Trivy的一个GitHub Action标签指向了一个恶意版本(v0.69.4)。这个版本的Trivy内置了窃取凭证的后门代码。
- 3月24日之前:LiteLLM的CI/CD流程中,恰巧通过
apt 命令安装了Trivy作为安全扫描工具,并且没有锁定具体的版本号。当CI运行时,便自动拉取并执行了恶意的Trivy版本。
- 恶意Trivy在CI中执行:它在CI环境中运行,扫描代码的同时,秘密窃取了环境变量中名为
PYPI_PUBLISH 的Token。这个Token赋予了向PyPI发布LiteLLM包的权限。
- 3月24日:攻击者使用窃取的Token,在10:39和10:52(UTC)分别上传了LiteLLM v1.82.7和v1.82.8两个恶意版本到PyPI。
两种截然不同的投毒手法
攻击者在两个版本中采用了不同的代码注入方式,后者尤为狡猾。
-
v1.82.7:直接注入
恶意代码被直接嵌入到 litellm/proxy/proxy_server.py 这个源文件中。只有当用户的代码执行 import litellm.proxy 时,恶意负载(Payload)才会被触发。
-
v1.82.8:利用Python启动钩子(.pth文件)
这个版本的手法升级了。攻击者在包的 site-packages/ 目录下放置了一个名为 litellm_init.pth 的文件。.pth 文件是Python的特殊机制,在Python解释器启动时就会自动执行其中指定的代码。
这意味着,只要安装了此版本,无论你是运行 pip install、启动IDE的语言服务器,还是执行任何调用Python解释器的操作,恶意代码都会无条件触发。更棘手的是,.pth 文件在wheel包的 RECORD 清单中是合法声明的,哈希值也匹配,因此即使使用 pip install --require-hashes 也无法防御。这种技术在MITRE ATT&CK框架中被归类为T1546.018(Python启动钩子)。
三阶段恶意载荷分析
对恶意代码进行解码后,可以发现其行动逻辑清晰,分为三个阶段:
第一阶段:信息收集与窃取
载荷会遍历系统,窃取大量敏感信息,包括:
- SSH私钥、
authorized_keys、known_hosts等SSH相关文件。
- 各类
.env环境配置文件。
- Git凭证(
git-credentials)和配置。
- Shell历史记录(可能包含手动输入的API密钥)。
- AWS、GCP、Azure等云服务的凭证文件(如
~/.aws/credentials)。
- Kubernetes的配置文件(
kubeconfig)和Service Account Token。

第二阶段:加密与外传
窃取的数据会被打包、加密,然后发送到攻击者控制的服务器(C2)。关键证据在于:
- C2域名
models.litellm.cloud 注册于攻击前一天(3月23日)。
- 用于加密的RSA公钥与之前Trivy、KICS等项目遭受攻击时使用的公钥完全相同,这成为了关联多起攻击的硬证据。

第三阶段:持久化与横向移动
为了维持长期控制并在网络中扩散,载荷会执行以下操作:
- 本地持久化:在用户目录下创建
~/.config/sysmon/sysmon.py 后门文件,并配置一个名为“System Telemetry Service”的systemd用户服务。该服务会每5分钟向 checkmarx.zone/raw 请求指令,获取并执行下一阶段的任务。
- Kubernetes横向移动:如果检测到运行在K8s环境中,脚本会尝试:
- 查找Service Account Token。
- 遍历所有命名空间,窃取Secrets。
- 在
kube-system 命名空间下,为每个节点创建一个特权Pod(使用 alpine:latest 镜像),将宿主机根目录挂载进去,并将 sysmon.py 后门部署到每个节点上。这些Pod的名称通常带有 node-setup- 前缀。
事件是如何被发现的?
这次攻击的曝光带有些许偶然性。安全研究员Callum McMahon在测试一个Cursor插件时,LiteLLM作为依赖被自动安装。安装后,他的机器出现卡顿和内存耗尽。经过排查,他发现是 .pth 文件触发了一个意外的 fork bomb:恶意代码启动的子进程再次触发Python解释器初始化,从而又执行 .pth 文件,形成了无限循环。这个漏洞导致的资源耗尽反而加速了恶意行为的暴露。
检测与修复指南
如何检查是否受影响?
你可以运行以下命令进行排查:
# 1. 检查已安装的LiteLLM版本
pip show litellm | grep Version
# 2. 检查后门文件是否存在
ls -la ~/.config/sysmon/sysmon.py
ls -la ~/.config/systemd/user/sysmon.service
# 3. 检查临时文件残留
ls /tmp/tpcp.tar.gz /tmp/session.key /tmp/payload.enc
# 4. 检查所有.pth文件中是否包含可疑的base64或exec代码
find $(python3 -c “import site; print(' '.join(site.getsitepackages()))“) -name ”*.pth“ -exec grep -l ”base64\|exec“ {} \;
# 5. 检查Kubernetes集群中是否存在可疑Pod
kubectl get pods -A | grep node-setup-
修复建议
- 如果未安装恶意版本:立即将LiteLLM版本锁定在
<=1.82.6。
- 如果已安装恶意版本(1.82.7/1.82.8):请注意,仅仅升级到安全版本是不够的,因为恶意代码在安装时就已经执行。必须执行以下完整清理流程:
- 删除后门:移除后门文件
~/.config/sysmon/sysmon.py 及其相关的systemd服务。
- 轮换所有凭证:这是最关键的一步!必须假设SSH密钥、云服务访问密钥、Git令牌、数据库密码、API密钥等所有敏感凭证均已泄露,并立即进行更换。
- 检查K8s集群:彻底清查集群中是否有名为
node-setup-* 的恶意Pod,并检查节点系统是否被植入后门。
- 重建环境:对于生产或重要环境,建议直接在干净的基础设施上重新部署,而非在可能已受污染的原有系统上修补。
反思与启示
本次LiteLLM投毒事件如同一场精心策划的“数字多米诺骨牌”,暴露了开源供应链和现代研发流程中的多重风险点:
- CI/CD流程安全缺位:GitHub Actions的
pull_request_target 事件需谨慎使用;在CI中通过包管理器安装工具时,必须锁定版本,避免使用 latest 或动态标签。
- Python生态特定风险:
.pth 文件作为一种强大的持久化机制,目前 pip 等工具对其内容缺乏有效安全审查,这为Python供应链安全埋下了隐患。
- 攻击面转移:流行的AI/ML工具链(如LiteLLM)和安全/渗透/逆向工具(如Trivy、KICS)因其高价值和高权限,正成为供应链攻击的新焦点。
- 攻击的专业化与体系化:攻击者在Trivy、KICS和LiteLLM等多个项目中使用了相同的RSA密钥、相同的Payload命名(
tpcp.tar.gz)和相同的通信模式,表明背后是一个有组织、有资源的攻击团队在实施体系化作战。
对于开发者和运维团队而言,这次事件是一次沉重的警醒。它要求我们重新审视从依赖管理、CI/CD配置到运行时监控的每一个安全环节。在云栈社区等技术论坛中,关于如何加固供应链、安全使用第三方库的讨论也变得愈发重要。在享受开源红利的同时,构建主动、纵深的安全防御体系已不再是可选项,而是必答题。
|