1. LiteLLM失守意味着什么:AI网关是攻击者最想要的落点
1.1 AI网关在基础设施里的特殊位置
很多团队对AI基础设施的安全认知,还停留在“防Prompt注入”或者“给模型加护栏”这个层面。但真正搞过生产环境的人都知道,一个AI应用的调用链往往长得多:业务服务 → API网关 → 模型网关 → 各家模型服务商 / 私有化模型推理服务,中间还穿插着缓存、向量数据库、鉴权服务、成本统计组件。
LiteLLM这类产物刚好卡在整条链路最关键的十字路口上。它的核心功能是提供一个统一的代理层,把OpenAI、Anthropic、Cohere、本地vLLM这些来源不同的模型接口收敛成一个标准入口,顺带帮你做API Key管理、请求转发、成本统计、限流、缓存。简单说,业务代码不需要关心背后接的是哪家模型,只要打给LiteLLM就行。
这个位置的杀伤力是双面的。对工程师来说,它是再方便不过的抽象层;可对攻击者来说,它等于一个“全站流量的合法旁路”。只要控制住这个网关,就不需要费劲去打业务系统,因为所有请求、所有密钥、所有上下文本就都在它手里。
我见过不少团队把LiteLLM当作一个普通开源组件随手部署,觉得“反正它就是一个转发用的工具”,完全不设防。这类组件一旦出事,往往比直接打业务服务还致命。
1.2 “AI软件被投毒”要拆成两层看
这次事件标题里有个词很容易被带过去——“AI基础设施被投毒”。它其实包含两层含义,我建议每个做AI工程的人把这两层分开理解。
第一层是软件供应链投毒。攻击者往开源生态里投放恶意依赖包,或者劫持上游维护者账号后发布带后门的版本,最终让受害者的生产环境在升级/安装依赖时把恶意代码拉进来。LiteLLM本身是开源项目,又通过pip这样的包管理器分发,天然处于这类攻击的射程范围内。这一层和传统软件供应链攻击在原理上没有本质区别,只是受害者变成了AI基础设施。
第二层是模型/数据层面的投毒。这属于AI特有的攻击面,比如污染训练数据、篡改权重、给缓存响应注入恶意内容。模型网关恰好能把这两层连起来——如果攻击者控制了网关,他不光能拿到代码层面的权限,还可以直接篡改发往模型服务的请求,或者替换缓存里的响应,让业务在“模型输出一切正常”的错觉下持续收到恶意结果。
这两层叠加在一起,才是这次事件真正值得警惕的地方。传统安全团队通常只盯系统漏洞,AI团队往往只关心模型效果,而LiteLLM恰好落在两拨人的责任空档里。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从供应链投毒到网关失守:完整攻击链拆解
2.1 投毒入口最常发生在哪几个环节
先说一个值得所有团队反思的事实:大多数供应链攻击不是通过什么高级零日漏洞打进来的,而是利用了“信任”。
从公开信息里可以看到,这类攻击最常见的入口是包名混淆和依赖投毒。攻击者会在PyPI、npm这些公共源上注册一个与热门项目同名或近似的恶意包,或者想办法让某个间接依赖被替换成恶意版本。由于LiteLLM需要对接大量的模型服务商SDK,它的依赖树非常庞杂,任何一个中间层依赖被污染,最终编译进容器镜像里的代码都可能是带后门的。
第二个常见入口是维护者账号被劫持。攻击者拿到上游维护者的账号后,会发布一个“看似正常”的新版本,这个版本包含正常更新和隐蔽后门,等到受害者跑pip install时自动拉取。
第三个不太容易被注意到的入口是镜像层投毒。很多团队直接从Docker Hub拉现成的LiteLLM镜像部署,而镜像里的每一层都是云上构建的,你根本无法确定某一层在执行时究竟做了什么。如果上游仓库的Dockerfile或GitHub Action流水线被篡改,你拉下来的镜像就已经是“被加工过的产品”了。
2.2 攻击者的操作路径:关键节点逐步还原
结合过去几年真实发生的供应链攻击案例,我试着把这条攻击链还原一下,帮大家理解“网关失守”是怎么一步步实现的。
第一步,投放恶意依赖。攻击者选定几个LiteLLM的间接依赖项,在公共包仓库发布恶意版本。为了不被立刻发现,恶意代码会隐藏得很深——比如不在安装时立即触发,而是延迟到容器启动后某个随机时间才开始运行。
第二步,等待受害者更新。受害者的CI/CD流水线在构建镜像时执行pip install,不加锁版本的情况下拉到了恶意版本。恶意代码随容器镜像一起被构建、推送、部署。
第三步,容器启动后恶意代码激活。它做的事情往往是先“摸索周围环境”——读取环境变量、查找Kubeconfig文件、扫描内网IP段、探测常见服务端口。Kubernetes Pod里默认挂载的ServiceAccount Token、预设的各种API Key,都是它重点寻找的目标。
第四步,利用网关的高权限向集群内部渗透。LiteLLM网关为了完成模型调用,往往绑定了对象存储、数据库、可观测组件等一大堆外部服务的凭证。攻击者拿到这些凭证后,就相当于站在了所有AI服务的入口位置。
第五步,横向扩散到Kubernetes集群。拿到初始立足点之后,攻击者会利用集群内配置不当的权限、默认挂载的Token、过宽的RBAC授权,从单一Pod逐步扩展到整个命名空间甚至整个集群。
2.3 为什么这个链条很难被提前发现
这个问题我从实战角度多聊几句。很多团队总觉得“我们有安全团队,有扫描工具,怎么会发现不了”。
核心问题在于:容器镜像构建完的那一刻,恶意代码就已经被“合法地”烘焙进镜像里了。常规的镜像扫描工具(Trivy、Clair等)主要扫的是已知CVE漏洞和特征库里的恶意文件,对“看起来是正常业务代码但行为可疑”的轻量后门几乎无能为力。换句话说,扫描工具是识别“已知的坏”,识别不了“看似正常的坏”。
第二层原因在于运行时的检测盲区。容器里没有传统的EDR或者说安全Agent,很多安全团队对容器内进程、网络外联的可见性几乎为零。恶意代码只要不触发明显异常——比如不占用过高CPU、不频繁崩溃,它就完全可以隐藏在正常业务流量之下运行几周甚至几个月。
第三层是人的原因。网关这类组件往往被当成“基础设施的一部分”而非“业务的一部分”,运维团队觉得它由开发团队负责,开发团队觉得它只是个工具,安全团队又不一定了解LiteLLM的技术细节。三个团队都在等别人发现异常,结果就是谁都没发现。
我建议所有团队做一次“供应链攻击复盘演练”:假设你们正在用的模型网关依赖里有一个包是恶意的,你们能不能在24小时内定位到攻击者拿到了什么、访问了哪些资源?如果答案是否定的,那你的安全建设就存在系统性空档。
3. Kubernetes横向扩散的底层逻辑:为什么集群成为攻击者的第二个战场
3.1 AI基础设施与Kubernetes的高度耦合
现在的AI应用栈,几乎是无缝长在Kubernetes上的。GPU节点调度靠K8s、推理服务的弹性伸缩靠K8s、模型网关以Deployment或者Pod形式跑在K8s里、向量数据库和Redis缓存大概率也在同一个集群里。有些团队甚至把训练任务也托管在K8s上,一个集群同时承载了开发、测试、生产环境。
这种高度耦合带来一个必然结果:攻击者不需要“攻破某个具体的AI服务”,他只要在集群里找到一个立足点,剩下的都可以通过集群内部的信任关系来扩张。
LiteLLM网关Pod被攻陷后,攻击者站在一个非常有利的位置。他运行的容器很可能拥有一个ServiceAccount,而Kubernetes会默认把ServiceAccount Token自动挂载到Pod里。很多集群管理员从未针对不同工作负载细化RBAC权限,结果就是网关Pod的Token权限大得惊人——可以读取整个命名空间的Secret、可以列出集群里所有Pod、甚至拥有创建Pod的权限。
3.2 横向扩散的关键路径与权限放大过程
Kubernetes里的横向扩散路径,我归纳下来主要有这么几条,每条都是真实事件里反复出现过的。
第一条路径是凭证窃取和复用。攻击者拿到Pod控制权后,第一件事就是翻环境变量、翻挂载的配置文件、翻Kubeconfig。很多应用为了方便,会把数据库密码、对象存储的AccessKey、甚至是生产环境的ServiceAccount Token都写进环境变量里。容器里的恶意进程读取这些信息,跟在服务器上读/etc/shadow差不多。
第二条路径是不安全的Kubelet端口。有些集群为了排查方便,把Kubelet的只读端口甚至10250主端口暴露在容器网络可访问的范围内,而且没有启用认证。攻击者一旦发现集群内某个节点存在这个问题,就可以直接调用Kubelet的API在节点上执行任意命令。这一步完成之后,攻击者就彻底跳出了容器边界,掌握了整个节点。
第三条路径是创建恶意Pod。如果网关Pod的ServiceAccount有create pods权限,攻击者可以提交一个带有特权模式、挂载了宿主目录的恶意Pod到集群里。这个Pod一旦运行,攻击者就能读取节点上的所有文件,包括kubelet的证书、其他容器的数据。这种方式比我上面说的Kubelet利用更隐蔽,因为新创建的Pod会混在大量日常Pod里,不仔细看很难定位。
我去年处理过一起类似的渗透测试案例,攻击者就是通过一个被攻破的普通业务容器,发现它挂载的Token有list secrets的权限,然后在一小时内把所有命名空间的Secret全部dump了出来。整个过程没有触碰任何漏洞,就是纯粹地利用了“默认权限过高”和“无人在意访问审计”这两个配置弱点。
3.3 一个容易被忽略的事实:集群内服务间的信任
我经常在分享时问一个问题:你们的K8s集群里,Pod和Pod之间能互相访问吗?默认情况下,Kubernetes集群没有启用NetworkPolicy时,所有Pod之间是可以自由通信的。这意味着,攻击者拿下一个网关Pod,就能对集群内所有服务的ClusterIP发起扫描和探测,找到数据库、找到另一个管理后台,然后进行端口扫描和弱口令爆破。
很多团队觉得自己“没有对外暴露数据库,所以是安全的”,但忽略了集群内部的网络是默认互通的。攻击者所有的横向动作都发生在集群内部,外部防火墙根本看不到异常流量,入侵检测设备也只会记录“集群内节点到节点之间的正常通信”。
真正解决这个问题的唯一办法是默认拒绝的NetworkPolicy,或者整套服务网格的mTLS。但这两样东西在生产环境的落地成本都不低,所以很多团队直到出事之前,都选择性地忽略了集群内部的横向移动风险。
4. 实战排查:从发现集群异常到定位可疑后门的完整过程
4.1 第一步:从流量和外联行为找突破口
如果怀疑网关被投毒,最直接的排查切入点是看网络外联。正常业务容器只应该访问少数几个服务——模型API的域名、内部数据库、监控采集端点,如果出现了大量陌生IP或可疑域名,那大概率有问题。
实际排查命令也很简单,进到容器里看一眼网络连接:
bash复制kubectl exec -it <pod-name> -n <namespace> -- netstat -anp
kubectl logs <pod-name> -n <namespace> --tail=500
如果容器里没有netstat,可以用ss -anp,或者直接看节点上的连接记录。重点观察有没有到海外IP、动态域名、非标准端口的长连接。很多恶意代码会优先选择域名外联,因为IP会被安全策略封禁,而域名可以频繁切换解析。
DNS请求也是一个重要观察点。如果你的集群里有DNS审计能力,可以查一下网关Pod是否解析过一些看起来“随机生成”的子域名,这种情况通常意味着恶意代码在做数据外带或者指令下发。
4.2 第二步:用审计日志还原攻击路径
光看流量只能确认“有异常”,要还原攻击者的完整路径,必须依靠Kubernetes审计日志。审计日志里记录了谁在什么时间访问了API Server、做了什么操作、访问了哪个资源。启用方式是在kube-apiserver的启动参数里加上:
bash复制--audit-log-path=/var/log/kubernetes/audit/audit.log
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
审计策略建议最少开启RequestResponse级别,这个级别会记录请求的完整响应内容。虽然日志量会变大,但对事故处理和溯源几乎不可替代。
排查时优先看几类操作:list secrets、get secrets、create pods、create deployments、对RBAC对象的修改。攻击者为了摸清集群里有哪些可利用资源,通常会先做大量的list操作。如果发现某个ServiceAccount在非业务时间高频读取Secret,那基本可以断定这个Token已经被人拿来当肉鸡了。
4.3 第三步:定位具体恶意对象
定位恶意Pod比定位异常流量更难,因为攻击者通常不会用一眼就能看穿的Pod名。常见的隐蔽手段是按业务风格命名,比如redis-cache-01、model-inference-worker这类看起来人畜无害的名字。
这时候要从镜像和标签入手。在Kubernetes里查看所有工作负载使用的镜像来源,重点关注有没有非私有仓库的镜像、有没有tag为latest或漂移频繁的镜像、有没有近期才被新创建的Deployment。正常团队的生产环境应该有严格的镜像准入规则,凡是镜像仓库不属于自家私有仓库的Pod都应该是高危信号。
排查命令:
bash复制kubectl get pods -A -o custom-columns=NAMESPACE:.metadata.namespace,POD:.metadata.name,IMAGE:.spec.containers[*].image | sort | uniq
同时别忘了一个与热搜关键词直接相关的细节:Kubernetes调用containerd的方式。从K8s 1.24开始,默认容器运行时基本都以containerd为主,Pod的整个生命周期——从拉镜像、创建容器、启动进程到销毁容器——都由kubelet通过CRI调用containerd完成。排查时如果想知道某个Pod是什么时候被创建、从哪里拉取的镜像、启动命令有没有被修改,直接看containerd的日志和记录是最可靠的:
bash复制journalctl -u containerd --since "2025-01-01" | grep <pod-name>
crictl ps -a | grep <可疑关键词>
crictl images
crictl是排查容器运行时状态的核心工具,它能列出所有容器、镜像、镜像的拉取来源。很多恶意镜像在被删除后还能从containerd的存储目录里找到残留层,做取证时特别有用。
4.4 事件处置与止损清单
确认攻击事实后,行动要快,但动作不能乱。下面这个处置优先级是我在多次实战中验证过的,强烈建议按顺序执行:
| 优先级 | 动作 | 目的 |
|---|---|---|
| P0 | 把可疑Pod/Deployment的副本数缩到0,或从命名空间上切断外网访问(NetworkPolicy) | 控制横向扩散,保住其他业务 |
| P0 | 轮换所有可能被泄露的密钥和Token,包括数据库密码、对象存储Key、模型服务商API Key、ServiceAccount Token | 切断攻击者的持续访问通道 |
| P1 | 对所有Pod的ServiceAccount做一次权限复核,临时回收高危权限 | 防止攻击者二次进入 |
| P1 | 对网关依赖树和镜像做取证分析,保留镜像快照和容器日志副本,不要急着删除现场 | 溯源和复盘用 |
| P2 | 重建网关服务,使用干净的镜像,并锁死依赖版本 | 恢复正常业务 |
| P2 | 排查集群内是否有异常Pod留存,清点所有被创建/修改的资源对象 | 清除持久化后门 |
这里特别提醒一件事:不要一发现异常就立刻把Pod删掉。很多团队出于本能反应,看到可疑容器第一时间删了,结果就是攻击者的痕迹、行为数据、网络连接记录全部一起消失,后面想溯源就只剩一地鸡毛。正确做法是先做镜像快照、复制容器日志、记录节点进程状态,然后再考虑清理。
5. 供应链与集群运维的双重加固:我个人的落地实践
5.1 供应链侧:版本锁定、私有镜像仓库、SBOM
先说供应链侧,这部分是“防患于未然”的根基。
第一要务是锁定依赖版本。我看到很多团队的Dockerfile还在用pip install litellm这种裸安装方式,每次构建拉到的版本完全不可控。正确做法是requirements.txt里精确到小版本甚至是commit哈希,配合lockfile一起管理。这样即使上游仓库被投毒,只要你不主动升级,恶意版本就不会自动进入生产环境。
第二要务是使用私有镜像仓库并开启镜像扫描。所有构建产物只推送到企业内部仓库,生产环境只允许从这个仓库拉取镜像。配合准入控制器(ImagePolicyWebhook)在Kubernetes级别强制检查镜像来源,能从机制上杜绝从公共源直接拉取带毒镜像的可能。
第三要务是生成和维护SBOM(软件物料清单)。使用Syft这类工具为每个镜像生成一份依赖清单,配合持续扫描,能在某个依赖爆出安全通告后立刻定位到受影响的镜像和部署。这一步听起来繁琐,但真正出事的时候,它能帮你省下几天的排查时间。
5.2 集群侧:最小权限、网络策略、审计
集群内部的加固,核心是三点。
第一点是ServiceAccount的最小权限。我之前见过太多部署,业务Pod一直用默认的default ServiceAccount,不仅没有细粒度的RBAC配置,还享受自动挂载Token的“特权”。正确的做法是为每个工作负载创建独立的ServiceAccount,只授予它完成本职工作所需的最小API权限,同时显式设置automountServiceAccountToken: false,除非真的需要从Pod里访问API Server。
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: litellm-gateway
spec:
template:
spec:
automountServiceAccountToken: false
serviceAccountName: litellm-gateway-sa
第二点是网络默认拒绝。Kubernetes的NetworkPolicy如果不主动配置,默认行为就是允许所有Pod互相通信。这个默认策略对安全是极大的威胁。我建议至少为每个命名空间配置一个默认拒绝的NetworkPolicy,然后显式放行业务必需的访问路径。即使做不到全量治理,至少也要把网关所在命名空间和其他命名空间之间的访问全部收紧。
yaml复制apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
第三点是审计与异常检测。在Kubernetes层面开启审计日志只是第一步,更关键的是有人去看这些日志。我建议把审计日志接入企业的SIEM平台,针对list secrets、create pods、update clusterrole这类高危操作设置告警。很多团队说“我们开了审计日志,但没人看”,那跟没开没有任何区别。
5.3 落地顺序与常见成本权衡
安全建设最怕一步到位,团队一旦觉得“要做的事情太多”就容易躺平。我的建议是按风险排序,分三个阶段落地。
第一阶段先做信息可见性。把网关、模型服务、数据库这些核心组件的日志全部收到一个统一平台,开启K8s审计日志,梳理出集群里所有ServiceAccount的权限清单。这一阶段不改变任何现有架构,成本最低,但价值最大——出事的时候你至少能知道发生了什么。
第二阶段做权限收紧。给核心工作负载创建独立ServiceAccount、关掉自动挂载Token、删除不必要的ClusterRoleBinding。这一阶段可能需要业务配合做一些小改动,但大部分工作就是改YAML配置,收益非常高。
第三阶段做网络层面的隔离和准入控制。配置NetworkPolicy默认拒绝、引入OPA/Gatekeeper这类准入控制器、强制镜像来源校验。这一阶段改动面大,可能影响现有业务通信方式,需要安排在排期宽松的时间窗口推进。
我见过一些团队把大量精力花在模型层的安全上——加Prompt防火墙、做内容审核、盯着模型的输出是否符合价值观——这些当然有意义,但是当攻击者通过供应链已经控制了你的网关时,模型层做再多的安全防护也无济于事。模型可以被替换、被绕过,而网关一旦失守,整个AI服务的钥匙就到了别人手里。
这次LiteLLM事件是一个很典型的提醒:AI基础设施的安全边界不应该从模型开始画,而应该从运行这些模型的基础组件开始画。我的建议很直接——今晚回去打开你们的模型网关项目,看看它的依赖树、镜像来源和运行中的ServiceAccount权限,大概率会有令人意外的发现。
