AI基础设施安全:云、容器与GPU逃逸
前几篇的攻击落在模型、Agent、RAG 或供应链上。这篇要写的是更底层的场景:攻击者根本不碰模型,直接从基础设施的配置错误拿走整个环境。 一个 SSRF 拿到云账号凭证、一条 IAM 链提权到管理员、一个 Pod 里的 ServiceAccount Token 直接调 K8s API,最后从 GPU 容器逃逸到宿主机。每一步用的都是已有的功能或默认配置,不是漏洞利用。
AI 系统特别容易命中这些问题,原因是它天然把三类高危组件绑在一起:有出站 HTTP 请求能力(URL 抓取、插件调用)、跑在云上且需要 IAM 角色访问存储和模型 API、部署在 K8s 里并挂载 GPU。三者叠加后,攻击面不是加法,是乘法。
一、从 SSRF 到云凭证
AI 服务几乎都有出站 HTTP 能力:Agent 的 web_fetch 工具、RAG 的 URL 入库、Webhook 回调、OpenAPI 插件的远端 schema 加载。攻击者把这些功能的 URL 参数指向云元数据端点,就能拿到当前实例的 IAM 临时凭证。
云厂商的元数据端点
| 云厂商 | 端点 | 版本 | 鉴权要求 |
|---|---|---|---|
| AWS | http://169.254.169.254/latest/meta-data/ |
IMDSv1(默认开放) | 无 |
| AWS | 同上 | IMDSv2 | PUT + Token header |
| 阿里云 | http://100.100.100.200/latest/meta-data/ |
v1 | 无 |
| GCP | http://metadata.google.internal/computeMetadata/v1/ |
v1 | Metadata-Flavor: Google header |
| Azure | http://169.254.169.254/metadata/instance?api-version=2021-02-01 |
— | Metadata: true header |
IMDSv1 是重灾区:不需要特殊 header,GET 即可。如果目标服务部署在 AWS EC2 上且安全组没拦元数据端点,一条 SSRF 就够了。
场景:一个企业内部 RAG 系统,POST /api/knowledge/import 接受 URL 参数并抓取内容入库。抓取逻辑是 Python requests.get(url),没有做内网地址过滤。攻击者构造:
1 | POST /api/knowledge/import |
响应进入 RAG 的切片管道,返回一个包含角色名的列表:
1 | ["ai-inference-role"] |
下一步把角色名拼进路径,拿凭证本体:
1 | POST /api/knowledge/import |
1 | { |
这三个值组成一组临时 STS 凭证,有效期六小时。有效期内,攻击者在任何有网的地方都能以这个角色调用 AWS API。
检测困难的原因
传统 SSRF 的目标是内网服务,流量模式是「外部 IP → 内部 IP」。云元数据 SSRF 的目标是 169.254.169.254,这个地址不走网关,云平台侧没有访问日志。防守方看到的是:一个外部用户发起了一个正常的 URL 导入请求,RAG 抓取了一个不可路由的 link-local 地址,然后无后续。如果没有在应用层对出站 URL 做过滤和审计,这个请求完全静默。
防御不是加 WAF 规则——攻击者可以用重定向、DNS Rebinding 或 IPv6 映射绕过。结构性修复是在抓取代码里做 URL 解析后白名单:只允许 https:// 协议 + 外部域名,拒绝所有私网/保留 IP 段(169.254.0.0/16、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10、fd00::/8)。再在基础设施层启用 IMDSv2 并设 HttpTokens=required、HttpPutResponseHopLimit=1,使容器内的请求无法到达元数据端点。

二、IAM 角色链提升
拿到一组临时凭证后,第一步是搞清楚这个角色能做什么。sts get-caller-identity 不需要任何权限:
1 | AWS_ACCESS_KEY_ID=ASIA3FK7XQJ2MKPNETVQ \ |
1 | { |
拿到 Account ID 和角色名。下一步枚举这个角色的实际权限:
1 | aws iam list-attached-role-policies --role-name ai-inference-role |
枚举结果可能长这样:
1 | -- S3 -- |
关键发现是 sts:AssumeRole 被授到了一个更高权限的角色上。AI 推理服务经常需要临时切换角色去拉模型权重或调其他服务,运维就给了它一个 AssumeRole 信任关系——但目标角色可能是管理员级别的。
利用:
1 | aws sts assume-role \ |
1 | { |
用这组凭证再次 get-caller-identity,确认角色已切换到 eks-admin-role。这个角色如果绑定了 eks:DescribeCluster + eks:ListClusters,就能直接拿到 EKS 集群的 kubeconfig:
1 | aws eks list-clusters --region us-east-1 |
至此,攻击者从一次 SSRF 开始,通过两条 IAM 链(实例角色 → AssumeRole → 集群管理角色),拿到了 K8s 集群的管理员 kubeconfig。全程没有利用任何软件漏洞,每一步都是合法 API 调用。
三、K8s ServiceAccount Token 窃取
即使攻击者没有走到 IAM 链那一步,只要能在任何一个 Pod 里执行命令(比如通过 Agent 的代码执行工具、MCP 服务器的 SSRF 回连、或一次容器内 RCE),ServiceAccount Token 就挂在固定路径上:
1 | cat /var/run/secrets/kubernetes.io/serviceaccount/token |
1 | eyJhbGciOiJSUzI1NiIsImtpZCI6Ik1UWkVOelF3TnpFMk9USXpOREE1TlRRd05qYzBNRGM0TlRneU1EWXpOREF4Tnprd056UTBNUSJ9... |
同时拿到 CA 证书和 Namespace:
1 | NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) |
直接调 API,列出当前 Namespace 的 Pod:
1 | curl -s --cacert "$CA" \ |
1 | { |
如果这个 SA 的 RBAC 绑定里有 get secrets,直接读:
1 | curl -s --cacert "$CA" \ |
1 | { |
1 | echo "c2stYmxpYmIxMjM0NTY3ODkwYWJjZGVmZ2hpams=" | base64 -d |
一个 OpenAI API key 和私有镜像仓库凭证同时到手。这是最低成本的横向移动:不需要漏洞,K8s 的设计就是把凭证放在 Pod 文件系统里的。
四、RBAC 过度授权与 Sidecar 共享卷
RBAC:最常见的过度授权模式
很多 AI 团队给推理服务的 ServiceAccount 绑了 cluster-admin,理由是「要读 ConfigMap、写 PV、拉镜像,懒得逐个配」。这等于把集群的 root 钥匙挂在了每一个 Pod 里。
典型的危险 RBAC:
1 | kind: ClusterRoleBinding |
攻击者拿到这个 SA Token 后,创建一个特权 Pod 把宿主机根文件系统挂进来:
1 | apiVersion: v1 |
1 | curl -s --cacert "$CA" -H "Authorization: Bearer $TOKEN" \ |
等 Pod 跑起来后 exec 进去读宿主机的 /etc/shadow:
1 | curl -s --cacert "$CA" -H "Authorization: Bearer $TOKEN" \ |
从这一刻起,攻击者持有宿主机 root 权限。Node 上所有 Pod 的环境变量(包含数据库连接串、API Key)、所有 SA Token、所有挂载的 Secret 都能读。
Sidecar 共享卷:间接渗透
不是每个 Pod 都给了 cluster-admin。更常见的场景是:Pod 里跑了两个容器——主应用和 Sidecar(日志采集、监控代理、Istio Envoy),它们共享一个 emptyDir 卷:
1 | spec: |
如果 Sidecar 容器被攻破(第三方镜像有漏洞、版本过老、配置不当),攻击者在 Sidecar 里可以读写 /var/log/app,即 /app/data——主容器的工作目录。如果这个目录里有模型缓存、session 文件或临时写入的 API 调用日志,攻击者不需要碰主容器就能拿到数据。
反过来也成立:如果主容器的模型加载逻辑会从共享卷里热更新文件(例如定时加载新的 LoRA 适配器),攻击者在 Sidecar 里写一个恶意 .pt 到这个目录,下一次热加载就触发反序列化 RCE——010 写的供应链投毒在这里变成了基础设施层的攻击路径。

五、GPU 容器逃逸:CVE-2025-23266
AI 推理容器和普通容器的区别在于 GPU 设备的挂载。K8s 里 GPU Pod 由 NVIDIA GPU Operator 管理,底层用 NVIDIA Container Toolkit 在容器创建时把 /dev/nvidia* 设备和驱动库注入容器。这个注入过程通过 OCI hook 机制实现——createContainer 和 prestart 钩子在宿主机上以 root 权限执行。
CVE-2025-23266(CVSS 9.0 Critical,AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)出在这个 hook 链上:攻击者如果能控制容器的 OCI spec 中传给 hook 的环境变量或参数,就能让 hook 在宿主机上执行任意命令。
利用条件
这个漏洞不是从容器内直接触发——需要攻击者能影响容器创建时的配置。在 K8s 场景里,等价于:
- 拥有某个 Namespace 内
create pods的 RBAC 权限 - 节点安装了受影响版本的 NVIDIA Container Toolkit(≤ 1.17.7)或 GPU Operator(≤ 25.3.0)
- 创建的 Pod 调度到有 GPU 的节点上
上一节写的 cluster-admin 或 create pods 过度授权,正好满足条件 1。
修复状态
| 项目 | 受影响版本 | 修复版本 |
|---|---|---|
| NVIDIA Container Toolkit | ≤ 1.17.7 | ≥ 1.17.8 |
| NVIDIA GPU Operator | ≤ 25.3.0 | ≥ 25.3.1 |
截至写作时的最新稳定版是 Container Toolkit 1.20.1(2026-09-19 发布),修复已合入超过一年。但大量集群的 GPU Operator 是部署时锁定的版本,之后没有跟进升级。
怎么检查自己是否受影响
在 GPU 节点上执行:
1 | nvidia-ctk --version |
攻击在整体链路中的位置
回到这篇文章的攻击链:SSRF → IAM 凭证 → AssumeRole 提权 → EKS kubeconfig → create pods → 构造恶意 Pod → NVIDIA Container Toolkit hook 在宿主机以 root 执行。最后一步拿到了节点级别的 root shell,不只是容器内权限。 有了宿主机 root,可以读取该节点上所有 Pod 的内存、网络流量、文件系统——包括其他 Namespace 里的数据库 Pod 和 Sidecar。
即使整条 IAM 链没有打通,攻击者只要通过任何手段拿到一个 create pods 权限的 SA Token(上一节的 Token 窃取路径),再叠加这个 CVE,就能从容器内走到宿主机。这两条路径是独立的,防御必须同时覆盖。
汇总:攻击链各环节的比较
| 环节 | 需要的前提 | 利用的机制 | 拿到的权限 | 检测难度 |
|---|---|---|---|---|
| SSRF → 元数据 | 出站 HTTP + 无 URL 过滤 | 云元数据 API | 实例角色临时凭证 | 高(无网络层日志) |
| IAM AssumeRole | 枚举到信任关系 | 权限设计错误 | 更高权限角色 | 中(CloudTrail 有记录) |
| K8s SA Token | Pod 内代码执行 | K8s 设计(Token 挂载) | SA 对应的 RBAC 权限 | 低(文件读取无告警) |
| RBAC 提权 | SA 有 create pods |
RBAC 过度授权 | 集群内任意 Pod 操作 | 中(API 调用可审计) |
| GPU 容器逃逸 | create pods + 旧版 Toolkit |
OCI hook 注入 | 宿主机 root | 低(hook 执行无 K8s 告警) |
防御清单
SSRF
- 出站 URL 在代码层做协议 + 目标地址白名单,解析后拒绝所有 RFC 1918 / link-local / ULA 地址段。
- AWS EC2 启用 IMDSv2(
HttpTokens=required),HttpPutResponseHopLimit=1,容器内请求无法到达元数据端点。 - GCP / Azure 关闭不需要的元数据字段,或通过网络策略限制。
IAM
- 最小权限:AI 推理角色的策略只包含它实际需要的 S3 读、SQS 收发。禁止
*。 - 审计所有
sts:AssumeRole信任关系:用 IAM Access Analyzer 找出外部可 assume 的角色,确认每一对关系都有业务理由。 - 不要把高权限角色(如 EKS 管理角色)信任给低权限角色。用单独的跳板角色 + 临时提升流程替代。
K8s
- SA Token 自动挂载关闭:不需要调 K8s API 的 Pod 加
automountServiceAccountToken: false。这是最高性价比的单条配置。 - RBAC 按 Namespace 拆分,只用
Role不用ClusterRole(除非确实需要跨 Namespace)。禁止cluster-admin绑定给 ServiceAccount。 - Pod Security Standards 限制
privileged: true、hostPID: true、hostPath挂载。 - NetworkPolicy 限制 Pod 到 K8s API Server 的访问(除非确实需要)。
GPU / Container Toolkit
- 升级 NVIDIA Container Toolkit ≥ 1.17.8、GPU Operator ≥ 25.3.1(CVE-2025-23266)。
- 在 CI 里加一条版本检查:GPU Operator 部署清单中的镜像版本低于修复版本则构建失败。
- 用 CDI 模式替代 legacy 模式(NVIDIA 推荐),减少 hook 攻击面。
Sidecar
- Sidecar 镜像和主镜像一样走漏洞扫描和版本管理。
- 共享卷内不放敏感文件:session 数据、临时 API 日志、模型缓存用独立卷或 PVC,不与 Sidecar 共享。
- 评估是否真的需要 Sidecar——很多日志采集已经可以用 DaemonSet + hostPath 替代。
检测
- CloudTrail 里监控
AssumeRole事件的异常模式:同一凭证短时间内 assume 多个不同角色。 - K8s API 审计日志监控:从 SA Token 发起的
create pods(特别是带privilegedsecurityContext 的)、get secrets、exec。 - 在 GPU 节点上监控
nvidia-container-runtime-hook的执行参数是否包含非预期的命令或路径。