LiteLLM投毒事件分析与应急防护:从排查到加固 1. 这起投毒事件到底是怎么回事先给还没搞清楚状况的朋友补个背景。LiteLLM 是现在做 AI 网关最常用的开源项目之一很多团队拿它做统一入口对接 OpenAI、Anthropic、各种国产模型以及自建的本地模型服务转发请求、管 Token 配额、做预算控制。可以说凡是把 LLM 能力接入生产环境的团队大概率会在某层架构里见到它的身影。正因为这个位置太关键了它手里天然握着最值钱的东西一堆大模型平台的 API Key。假设你有一个跑了几百个线上服务的中型团队所有 Key 都存在 LiteLLM 所在那台服务器上或者存在它可读取的环境变量里——那这台机器一旦被拿捏就等于你家所有 AI 服务的钥匙全被复制了一遍。这次被曝光的投毒事件走的正是软件供应链里最经典的路径恶意代码混进了 PyPI 上某个版本的 LiteLLM 包当你执行pip install litellm或者依赖解析器把版本自动拉下来的时候中招的代码就跟着进了你的环境。安装完成后恶意脚本会搜集环境变量、配置文件里跟 API Key 相关的敏感信息然后外传到一个指定的远程服务器。整个流程悄无声息服务还能正常启动、正常转发请求日志里也看不出明显异常。从攻击者的视角看这性价比极高。不用打穿你的防火墙不用暴力破解只需要把恶意版本“伪装”成一个官方包的合法升级就能让所有主动安装它的人自己把钥匙送上门。而且受害的往往是运维同学和开发同学都还没来得及细看版本号的“日常升级”场景——很多人习惯直接拉 latest或者依赖的依赖里间接引入根本不会逐行去审新版本的代码。所以这件事的真实杀伤力不在于“某一个恶意版本被装上了”而在于它印证了一个残酷的现实AI 基础设施已经成为供应链攻击的头号靶子因为这部分代码访问的都是直接能变现的敏感凭证。你不需要是知名大公司只要你用 LiteLLM、LangChain 这类生态里流行的工具就有概率被无差别扫中。这篇文章我就以一线运维的视角把排查、处置、加固的完整流程讲清楚。别慌但也别拖。2. 中招了没有快速自查清单先冷静下来按顺序查。核心目的是搞清楚三件事装的是哪个版本、这个版本是不是恶意版本、恶意代码有没有执行过。下面每一步都附上我实际排查时会用的命令和判断标准。2.1 核对自己的版本号第一步先看装了哪个版本pip show litellm重点看 Version 那行。如果版本号恰好落在被点名的恶意版本区间比如网上曝光文章里反复提到的那个特定版本号附近你就需要高度警惕。不过这里有个关键提醒不要只看版本号因为攻击者有可能是通过抢占一个不存在的“未来版本号”来骗过依赖解析器的也就是说你本地显示的版本号可能看起来非常合理、甚至比官方最新还新但它就是假的。更可靠的判断方法是拿本地版本去跟 PyPI 官方页面的历史版本列表对一下。如果出现以下特征基本可以直接判定有问题本地版本号高于官方最新版比如官方最新是 1.68.8而你装的是 1.68.9官方发布历史里根本找不到你本地这个版本号版本号的发布时间跟你实际安装时间对不上比如你两周前装的包官方却在三天前才发这个版本2.2 检查 site-packages 里的可疑文件版本号对不上可以去翻实际落地的文件。先找到 LiteLLM 的安装路径python -c import litellm; print(litellm.__file__)然后进入到对应的目录重点看下面几个地方包的根目录下有没有近期生成的奇怪.py文件比如telemetry.py、utils.py之外多出来的一些看起来像是“日志上报”“指标采集”的模块有没有二进制文件、加密过的数据文件、或者看起来像混淆过的代码段用ls -la看时间戳如果包的整体修改时间跟官方发布日期对不上也说明文件被替换过我见过一种排查思路就是直接对比官方源码仓库里同名文件的内容。如果本地文件里有官方仓库里不存在的一段代码——尤其是包含 URL 地址、网络请求逻辑、base64 字符串的代码——那就非常可疑了。这里不建议直接在一个正在使用的生产环境里打开翻找而是应该先找一台隔离的机器或者直接把包下载下来做静态检查。2.3 排查运行痕迹和网络行为恶意代码只要执行过就会留下痕迹。在 Linux 机器上可以查几条# 查看当前是否有可疑的 python 进程在跑 ps aux | grep -i litellm # 查看网络连接里有没有连向可疑地址 netstat -tnp 2/dev/null | grep ESTABLISHED lsof -i -n -P 2/dev/null | grep python尤其是那些没有任何业务逻辑支撑、却持续保持外部连接的 python 进程要重点排查。恶意代码执行完通常有两种选择一次性外传数据或者驻留下来持续监听。如果遇到前者网络连接可能已经断开了这时候只看进程和连接就不够还得回到文件系统层去看有没有被改为开机自启、有没有被写进 cron 定时任务crontab -l ls -la /etc/cron.d/ /etc/cron.hourly/ systemctl list-unit-files | grep -iE litellm|python|tmp别嫌这一步繁琐。这类恶意包虽然大部分是“一次性偷数据”但也有部分会尝试在机器上留后门清理不彻底会让后面白干。2.4 查看依赖解析的源头如果你是在团队协作环境里中招先想想这个包是怎么进来的。查一下pip freeze或者poetry.lock、requirements.txt看看有没有间接依赖的情况。有些项目的依赖树很深LiteLLM 可能是某个 agent 框架的依赖被带进来的这会导致你根本没主动安装过它但它一样会执行。所以自查范围不要只限在“装了 LiteLLM 的机器”要扩到“装过任何会调 LLM 接口的 Python 服务的机器”。检查 lock 文件找到依赖源头才知道真正的引入路径后面打补丁的时候才能覆盖全部节点。3. 确认受影响后第一时间应急处理查到异常别慌按下面顺序来。顺序很重要因为我见过有人一上来就急着删包结果把攻击痕迹和取证线索全抹掉了后面要分析影响范围都没有依据。3.1 先隔离再清理第一步是切断影响面。把所有使用 LiteLLM 的服务的启动方式临时改成不自动拉起或者直接停掉相关容器 / systemd 单元systemctl stop litellm.service docker stop 容器名如果一时找不到具体是哪个服务引用了它可以用这条找出所有正在运行的 python 进程里加载了 litellm 的ps aux | grep python | grep -v grep在确认所有进程都停下来之后再卸载恶意版本pip uninstall litellm卸载之后还要再确认一遍 site-packages 里没有残留文件。有些恶意代码会把自己复制到别的目录或者修改同目录下其他模块的__init__.py来达到持久化目的。保险起见卸载完再搜一遍find / -name *litellm* -type f 2/dev/null | grep -v proc搜出来的结果里凡是位于非标准路径的、或者藏在临时目录里的都值得手动确认一下。3.2 轮换所有可能被偷走的密钥这才是整个应急流程里真正的重头戏。很多人在这一步上犯一个致命错误只换了 OpenAI 的 Key结果发现数据库密码没换云厂商的 AK/SK 没换过了两天被人用云主机挖矿了。先想清楚恶意代码的常规行为搜集环境变量、读取常见配置文件、扫描当前目录下的.env。这意味着所有在这个环境里出现过的东西都可能已经泄露不只是 LLM 的 Key。什么样的算“出现过”包括但不限于同一台机器上所有服务用到的数据库密码、Redis 密码云平台的访问密钥对AK/SK和临时安全令牌对外部 API 的认证凭证短信服务、对象存储、支付接口等私有仓库的拉取凭证、注册中心的账号密码所有长驻进程里通过环境变量注入的密钥所以正确的姿势是把这台机器上所有密钥统一视为已泄露挨个轮换。轮换是有先后的先换权限最大的也就是云平台的根用户凭证和主账号密钥再换各业务的 API Key最后换低权限的账号抽时间把不用的直接删掉。轮换之后立刻检查一遍各平台的用量明细。OpenAI、Anthropic、各云厂商的控制台都有用量记录重点看有没有来自未知 IP 的调用、有没有异常的模型调用量、有没有不是你自己业务产生的账单。3.3 升级到官方修复版本清理完旧版本重新安装时记得锁定版本号不要直接裸装 latest。先去 PyPI 官方页面确认当前实际的最新版本是哪一版再对照官方安全公告确认哪个版本包含了修复。安装时直接指定版本pip install litellm官方确认的修复版本装完马上验证 hash跟官方发布的哈希值比对一下pip download litellm修复版本 --no-deps -d /tmp/verify sha256sum /tmp/verify/litellm-*.whl用官方页面公布的 sha256 去核对一致才说明这个包是原厂正品。如果官方没有公布哈希那就用pip默认的校验机制同时注意检查你安装的包来源是不是pypi.org官方源有没有被镜像源或者内部源劫持。3.4 更新所有引入链路上的凭据和依赖如果你在一个微服务集群里问题往往不是一台机器。很多团队里 LiteLLM 是通过共享依赖被多个服务引用的每个服务有自己独立的虚拟环境但不一定有独立的密钥策略。清理工作做完后要把“litellm 的所有活跃版本”列出来逐一升级。可以用一条命令循环处理所有虚拟环境find . -name pyvenv.cfg -exec dirname {} \; | while read env; do $env/bin/pip install --upgrade --force-reinstall litellm修复版本 done这条命令会扫描项目下所有虚拟环境把 LiteLLM 统一升级到修复版本。别忘了一些 Docker 镜像里的环境升级完要重新构建镜像并重新部署旧镜像里的恶意代码在重新部署前一直存在。4. 这类事件背后的攻击逻辑看完了具体操作再说说这件事暴露出来的底层逻辑。理解了攻击者的视角你才知道防护应该往哪个方向使劲。4.1 为什么偏偏是 AI 网关这种项目传统供应链攻击喜欢挑用户量大的通用组件下手因为一处投毒、影响全域。LiteLLM 这类项目正好符合但比通用组件更“肥”的是它天然持有高价值凭证。攻击者拿到的不是一段可以逆向分析的二进制而是可以直接调模型烧钱的 API Key。云厂商的 Key 还能用来开机器挖矿数据库密码能拖库短信接口能薅羊毛刷验证码。我常说一句话你的基础设施里谁的凭证离钱最近谁就是最值得被攻击的目标。大模型 API Key 恰好是“离钱最近”的典型——拿到就能直接产生账单而且账单还是记在你头上的。4.2 投毒手法并不高级但极难预防从目前公开的分析来看这类攻击的手法并不复杂在合法的 PyPI 包名下面抢占一个“没人用过的版本号”塞进去一段脚本在安装时或者首次 import 时执行。代码里通常就干几件事遍历环境变量筛选名字里带KEY、TOKEN、SECRET、OPENAI、ANTHROPIC等关键字的变量读取常见路径下的.env、config.yaml、credentials.json等文件把搜集到的内容 base64 编码后拼成一条 HTTP 请求POST 到攻击者的服务器有的还会顺手把当前机器的 hostname、用户名一起上报方便后续定向渗透这套东西放在几年前安全圈叫它“信息窃取木马”在开源生态里已经出现过无数次。难点在于它是通过官方包管理器的正常更新流程进入你系统的没有绕过任何认证机制也没有利用任何系统漏洞。常规的杀毒软件、入侵检测系统根本拿它没办法因为安装一个包在系统看来就是一次正常操作。4.3 这类攻击盯上的是你的升级习惯说句不爱听的这次中招的人里面很多是栽在自己的“升级惯性”上。团队里如果完全不管版本、所有包都拉 latest一旦上游某个版本被投毒拉到的就是恶意版本。就算管版本如果只锁大版本不锁小版本比如litellm1.68依然可能被解析到恶意小版本。我见过太多生产环境的requirements.txt里写着litellm没有任何版本约束。依赖解析器一跑直接拉到最新只能祈求上游每次发版都是安全的——这显然不现实。这次事件之后希望大家能改掉这个习惯生产环境的每个依赖都必须锁到版本号最好连传递依赖也锁死。5. 以后怎么防供应链安全实操应急处理完之后下面这轮加固才是真正让你以后少熬夜的部分。我按优先级排序来讲条件有限时先做前面的。5.1 第一优先级锁版本、锁哈希、锁依赖树最便宜也最有效的防护就是让“未知来源的代码”根本没机会进到你的环境里。要做到这一点三件事缺一不可直接依赖全部用精确锁版本传递依赖通过 lock 文件锁死用pip freeze requirements.lock生成完整清单提交到仓库对关键依赖校验哈希用pip install --require-hashes -r requirements.txt强制校验其中--require-hashes这个参数可能很多人没用过。它要求 requirements 文件里每一个包都带上--hashsha256:xxx的完整哈希任何来源的包只要哈希对不上就装不上。这一条直接把“上游版本被替换”这条路堵死代价是每次升级依赖都要手动更新哈希建议配合 Dependabot 之类工具自动化处理。5.2 第二优先级用工具做自动化扫描现在扫描开源依赖漏洞的工具很成熟强烈建议在 CI 里加一道。我常用的组合是pip-audit扫描 Python 依赖已知漏洞可以直接对接 PyPI 的安全公告osv-scanner覆盖面更广除了 Python 还支持 npm、Go、Java 等生态一处接入、全局可查safety老牌工具企业内部部署可以配合私有漏洞库在 CI 里加这一步的成本很低本质就是一条命令跑一下失败就阻断发布。以 GitHub Actions 为例大致长这样- name: Scan dependencies run: | pip install pip-audit pip-audit -r requirements.txt这些工具虽然不能保证拦截所有投毒因为投毒版本往往还没被登记为“已知漏洞”但能拦住“被公开披露过的恶意版本”。这次事件曝光后主流扫描器的漏洞库里应该会很快同步所以扫描工具的价值在于下次有人踩坑公开之后你不需要手动检查CI 直接替你拦住。5.3 第三优先级控制密钥的暴露面你可能会说锁版本和扫描都做了但万一哪天又一个零日投毒呢所以要往深处再走一步就算恶意代码真的执行了能不能让它偷不到东西答案是能核心思路是缩小密钥暴露面。不用环境变量存一个服务用不到的密钥。很多团队图省事把所有平台的 Key 全塞到一台机器的环境变量里结果一台机器泄露全军覆没对 LLM 网关这类服务用专门的密钥管理工具比如 Vault、云厂商的 Secrets Manager动态下发密钥落到进程里的只是业务运行必需的几把钥匙给每个服务分配最小权限的专用 Key不开“全模型全权限”的通配 Key定期轮换即使泄露也能把这个密钥的影响窗口压到最小顺手再说一个大家经常忽略的很多人测试环境和生产环境共用同一套 Key。攻击者扫描到的 Key 如果是生产可用的那风险就直接放大到生产环境了。务必让测试环境使用测试专用的 Key甚至可以直接使用不会产生真实费用的测试额度。5.4 第四优先级建立内部包源和依赖审批如果团队规模允许搭建一个内部 PyPI 镜像比如用 devpi 或者 Nexus 做代理缓存所有机器统一从内部源安装依赖。这样有几层好处内部源可以设置“只允许白名单版本”的规则非白名单版本不允许安装可以在内部源和公网 PyPI 之间加一道人工审批新版本上线前先由安全同学审一遍所有机器的安装来源统一方便统一排查和统一升级这个方案不适合小团队但对稍微有点规模的公司来说投入产出比很高。如果你所在团队只有两三个人请优先把 5.1 和 5.2 做好已经能挡住绝大多数常规风险。5.5 第五优先级建立监控和响应预案最后这一条是心态层面的防不住不等于躺平关键是能快速发现和响应。具体建议在云平台和 LLM 服务商的控制台都开启用量告警比如单日调用量突增 10 倍、出现陌生模型名称、账单金额超阈值立刻短信邮件告警把这类事件写成内部应急预案什么条件触发、谁来处理、轮换哪些密钥、联系哪个平台客服提前列好一旦发现异常调用优先怀疑密钥泄露不要先怀疑“是不是业务流量上涨了”说实话这次投毒事件曝光之后真正高效处置的团队都是那些“预案早就写好、密钥轮换流程早就跑通过”的团队。平时多演练一遍出事的时候就能少慌一次。6. 排查过程中的常见坑和速查参考最后把实操里容易踩的坑按场景整理成表方便你直接照着排查。这些全是我自己在处理类似事件时遇到过的真实问题不是理论推演。现象可能原因处理方向pip show litellm显示版本比官方最新还新版本号被攻击者抢占立即停止服务按 3.2 轮换密钥卸载后重装官方版本版本号正常但文件时间戳异常包文件被篡改或本地包被手动替换对比官方仓库文件内容确认无异常后重新安装pip install litellm安装失败但提示依赖冲突内部源或镜像源缓存了恶意版本清理 pip 缓存切换官方源锁定哈希重装服务正常运行但网络连接异常恶意代码驻留或连接外部服务器查看lsof -i定位进程断开连接后隔离处理云平台账单暴涨密钥泄露后被外部调用先停 Key 再排查用量不要先找业务侧背锅只有一台测试机中招生产没装测试环境与生产共用密钥默认测试环境密钥已泄露全部轮换直接依赖没写 litellm 但还是中了通过传递依赖引入查看 lock 文件确认引入路径统一升级后重建 lock卸载完还有文件残留恶意代码做了持久化搜索全盘 litellm 相关文件检查 cron 和自启动项补充几个容易忽略的点换完密钥一定要确认旧密钥在服务商那边是“彻底删除”不是“禁用”。很多平台的禁用状态只是不再接受新调用但攻击者如果已经拿到了禁用后他一样会尝试其他攻击面重装之后用pip check确认依赖树没有破坏有些间接依赖可能因为版本变化导致不兼容提前发现比运行时崩溃好得多如果你使用了容器化部署记得把旧镜像从仓库里剔除或打上“已废弃”标签防止后续有人从旧镜像启服务我给团队做培训时经常反复强调一句话供应链攻击拼的不是谁的技术更牛而是谁更早发现、更快反应。工具和流程都是手段真正决定损失大小的是你从“中招”到“完成密钥轮换”之间的时间窗口。这次 LiteLLM 投毒事件给全行业敲了一次警钟。不管最后排查下来你有没有中招都建议把这个过程走一遍确认版本、核对来源、轮换一次密钥、把依赖锁定加进 CI。花不了半天但下次再遇到类似事件你就能淡定很多。