It was 03:22 AM. The cursor on the terminal was blinking, almost as if it were mocking me. While conducting a routine penetration test on a newly deployed Kubernetes cluster for testCompany, I had managed to snag a shell inside a standard application container. But the real game isn't just getting a shell—it's about breaking out of that cramped cage and seizing control of the host machine. When I checked the output of /proc/self/status, one single line signaled that the rest of my night was about to get very interesting: CapEff: 0000003fffffffff. Translation? I was looking at a cell started with the 'privileged' flag, where the guard was fast asleep.
We often fall into the trap of thinking of containers as 'lightweight virtual machines.' In reality, containers are just isolated processes. When a tiny crack appears between Linux kernel features like namespaces and cgroups that provide this isolation, the whole structure can come crashing down like a house of cards.
The Fatal Error: Privileged Containers
The --privileged parameter, which developer friends sometimes use to solve issues quickly, is essentially a self-inflicted wound to your security. This parameter grants the root user inside the container almost all kernel capabilities on the host.
As a Red Teamer, the first thing I do when I breach such a container is try to mount the host's disk to myself. The logic is simple: if I can access all devices on the host, why shouldn't I see the host's main partition?
# Defanged Escape Scenario
# Step 1: List available disks
fdisk -l
# Step 2: Mount the host's main disk (e.g., /dev/sda1) to a folder inside the container
mkdir /mnt/host_root
mount /dev/sda1 /mnt/host_root
# Step 3: Now I can access the host's /etc/shadow file
cat /mnt/host_root/etc/shadow
From this point on, it only takes me seconds to grab the sysadmin's password hash for cracking or write my own SSH key into /mnt/host_root/root/.ssh/authorized_keys.
Defense Note: Never, and I mean never, run containers with privileged: true. If specific hardware access is required, only add the relevant capability units using --cap-add.
Docker Socket: Putting the Keys to the Kingdom Under the Doormat
Another classic but still common mistake is mounting the /var/run/docker.sock file inside a container. Usually, CI/CD tools or monitoring apps need this to run 'Docker in Docker' (DinD). However, this file is the actual API that talks to the Docker daemon. An attacker with access to this socket can simply tell the Docker daemon: 'Create a new container for me that mounts the host's root directory.'
# A move an attacker with Docker socket access would make
docker -H unix:///var/run/docker.sock run -it -v /:/host_root alpine:latest chroot /host_root
The moment this command runs, the attacker is no longer in a container; they are the host machine itself.
Image Security: The 'Latest' Gamble
It’s not just the runtime that’s critical; the build phase is just as vital. When writing a Dockerfile, using FROM python:latest is like playing a dangerous game of chance. You never know what vulnerabilities the 'latest' tag might pull in tomorrow. Always pin your images to specific versions and scan them for known vulnerabilities before they ever hit your cluster.
