Podstawy chroot
Mon 07 January 2019 by TORGirenW tym poście omówię ręczne tworzenie chroot-a, uruchamianie w nim aplikacji a także wykorzystanie go wraz z usługą ssh, do ograniczania uprawnień użytkowników logujących się do serwera.
Czym jest chroot?
Chroot jest skrótem od change root i określa mechanizm zmiany katalogu bazowego dla uruchamianego procesu. Powoduje to, że dany proces oraz wszystkie jego procesy pochodne odnosząc się do ścieżki / odnoszą się do zmienionego katalogu.
Aktualny katalog root można sprawdzić w podsystemie proc. Dla przykładu: proces bash, o pid 8429, został uruchomiony w chroot. Jego katalog root widziany z systemu bazowego:
# ls -l /proc/8429/root
lrwxrwxrwx. 1 root root 0 01-05 11:04 /proc/8429/root -> /tmp/ch
Natomiast sprawdzając ten sam atrybut z wewnątrz chroot-a, otrzymamy
# ls -l /proc/8429/root
lrwxrwxrwx. 1 0 0 0 Jan 5 10:04 /proc/8429/root -> /
Wynika z tego, że proces uruchomiony w chroot nie ma wiedzy o tym, że jego root został zmieniony.
Jak zrobić pierwszego chroot-a?
Wzorem solucji do gier ze starych pism komputerowych, poprowadzę Cię "za rączkę" i stworzymy minimalnego chroot-a. Będę również pokazywał pojawiające się błędy, ponieważ ich występowanie w dużym stopniu wyjaśnia podejmowane kroki w celu stworzenia chroot-a.
Naszym pierwszym celem, będzie uruchomienie bash w chroot-cie. W tym celu musimy utworzyć katalog w którym będzie nasz nowy root, a następnie wgrać do niego plik binarny bash (najlepiej do podkatalogu bin, dla zachowania wyglądu zwykłego filesystem-u.
$ mkdir /tmp/first_chroot
$ mkdir /tmp/first_chroot/bin
$ cp /bin/bash /tmp/first_chroot/bin/
Następnie spróbujmy uruchomić bash-a w chroot-cie. Aby to zrobić, należy z użytkownika root wykonać polecenie chroot, a jako argumenty przekazać: katalog na który chcemy zmienic root-a oraz polecenie które chcemy uruchomić.
# chroot /tmp/first_chroot/ /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory
Otrzymaliśmy błąd, że nie ma takiego pliku ani katalogu. Jest to bardzo mylący błąd, ponieważ ten plik istnieje.
Dokładniej o tym problemie opowiem w innym odcinku. Na tą chwilę musimy tylko wiedzieć, żę w nagłówkach ELF bash-a, mamy wpis dotyczący lokalizacji INTERPRETER-a, który ma zostać uruchomiony dla tej aplikacji
$ readelf -a /bin/bash|grep INTERP -A2
INTERP 0x000154 0x00000154 0x00000154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
Jak widać, jako interpreter jest ustawiony linker. Ponieważ w chroot-cie proces jako / bierze katalog chroot-owy, dlatego musimy również skopiować linker:
$ mkdir /tmp/first_chroot/lib
$ cp /lib/ld-linux.so.2 /tmp/first_chroot/lib/ -iv
W tej chwili możemy ponownie spróbować uruchomić bash-a w chroot-cie.
# chroot /tmp/first_chroot/ /bin/bash
/bin/bash: error while loading shared libraries: libtinfo.so.6: cannot open shared object file: No such file or directory
Powyższy błąd oznacza, że linker próbuje załadować biblioteki współdzielone i nie jest w stanie ich zlokalizować. Dlatego trzeba je również dograć. Trzeba pamiętać, że poszukiwany plik jest najczęściej symlink-iem do konkretnej wersji biblioteki:
$ ls -l /lib/libtinfo.so.6
lrwxrwxrwx. 1 root root 15 2018-05-09 /lib/libtinfo.so.6 -> libtinfo.so.6.1
Dlatego, gdy kopiujemy potrzebne biblioteki, należy przekopiować zarówno symlink jak i samą bibliotekę
$ cp /lib/libtinfo.so.6 /lib/libtinfo.so.6.1 /tmp/first_chroot/lib -iv
'/lib/libtinfo.so.6' -> '/tmp/first_chroot/lib/libtinfo.so.6'
'/lib/libtinfo.so.6.1' -> '/tmp/first_chroot/lib/libtinfo.so.6.1'
Po przegraniu biblioteki, możemy ponownie spróbować przełączyć się do chroot
# chroot /tmp/first_chroot/ /bin/bash
/bin/bash: error while loading shared libraries: libdl.so.2: cannot open shared object file: No such file or directory
Widzimy, że teraz występuje problem z kolejną biblioteką. Aby nie wgrywać po jednej bibliotece i sprawdzać jakiej jeszcze brakuje, odczytajmy wszystkie potrzebne biblioteki i wgrajmy je za jednym razem. Aby odczytać potrzebne biblioteki, użyjemy polecenia ldd
$ ldd /bin/bash
linux-gate.so.1 (0xb7ede000)
libtinfo.so.6 => /lib/libtinfo.so.6 (0xb7d5e000)
libdl.so.2 => /lib/libdl.so.2 (0xb7d59000)
libc.so.6 => /lib/libc.so.6 (0xb7bb5000)
/lib/ld-linux.so.2 (0xb7ee0000)
Widzimy, że brakuje mam libdl.so.2, libc.so.6
$ cp -iv /lib/libdl.so* /lib/libc.so* /tmp/first_chroot/lib/
'/lib/libdl.so' -> '/tmp/first_chroot/lib/libdl.so'
'/lib/libdl.so.2' -> '/tmp/first_chroot/lib/libdl.so.2'
'/lib/libc.so' -> '/tmp/first_chroot/lib/libc.so'
'/lib/libc.so.6' -> '/tmp/first_chroot/lib/libc.so.6'
Teraz, gdy mamy wszystkie potrzebne biblioteki, możemy w końcu uruchomić naszą powłokę w chroot
# chroot /tmp/first_chroot/ /bin/bash
bash-4.4#
Widzimy, że została uruchomiona powłoka bash. Jednak, nie działają w niej żadne podstawowe polecenia systemu Linux: ls, mkdir, mount itp. Jest tak dlatego, że w naszym chroot mamy jedynie bash-a. Działają natomiast polecenia samej powłowki: cd, pwd itp.
Poszerzmy teraz naszego chroot-a o polecenie ls
$ cp -iv /bin/ls /tmp/first_chroot/bin/
'/bin/ls' -> '/tmp/first_chroot/bin/ls'
$ ldd /bin/ls
linux-gate.so.1 (0xb7f75000)
libselinux.so.1 => /lib/libselinux.so.1 (0xb7f04000)
libcap.so.2 => /lib/libcap.so.2 (0xb7efe000)
libc.so.6 => /lib/libc.so.6 (0xb7d5a000)
libpcre2-8.so.0 => /lib/libpcre2-8.so.0 (0xb7cd3000)
libdl.so.2 => /lib/libdl.so.2 (0xb7cce000)
/lib/ld-linux.so.2 (0xb7f77000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb7caf000)
$ cp -iv /lib/libselinux.so.1 /lib/libcap.so.2* /lib/libpcre2-8.so.0* /lib/libpthread.so* /tmp/first_chroot/lib/
'/lib/libselinux.so.1' -> '/tmp/first_chroot/lib/libselinux.so.1'
'/lib/libcap.so.2' -> '/tmp/first_chroot/lib/libcap.so.2'
'/lib/libcap.so.2.25' -> '/tmp/first_chroot/lib/libcap.so.2.25'
'/lib/libpcre2-8.so.0' -> '/tmp/first_chroot/lib/libpcre2-8.so.0'
'/lib/libpcre2-8.so.0.7.0' -> '/tmp/first_chroot/lib/libpcre2-8.so.0.7.0'
'/lib/libpcre.so.1.2.10' -> '/tmp/first_chroot/lib/libpcre.so.1.2.10'
'/lib/libpthread.so' -> '/tmp/first_chroot/lib/libpthread.so'
'/lib/libpthread.so.0' -> '/tmp/first_chroot/lib/libpthread.so.0'
gdy dogramy już aplikację ls oraz potrzebne biblioteki, możemy wykonać w naszym chroot polecenie ls. Warto przed tym ustawić odpowiedni zmienna PATH, gdyż niekoniecznie będzie ona ustawiona na katalog bin
# PATH=$PATH:/bin/
# ls -l
drwxrwxr-x. 2 1000 1000 80 Jan 5 13:52 bin
drwxrwxr-x. 2 1000 1000 360 Jan 5 13:56 lib
Tak przygotowany chroot zapewnia nam izolację procesów w nim uruchomionych od pozostałego filesystem-u.
Uruchamianie aplikacji w chroot
Jako przykładową aplikację, uruchomimy sobie wbudowany w python-a 3 server HTTP. Aby to zrobić, wkopiujemy plik binarny, potrzebne biblioteki systemowe oraz wszystkie pliki interpretera python (wartym rozważenia rozwiązaniem jest również instalacja danej aplikacji w odpowiednich katalogach, zamiast kopiowanie plików)
$ cp /usr/bin/python3.6 /tmp/first_chroot/bin/
$ ldd /tmp/first_chroot/bin/python3.6
linux-gate.so.1 (0xb7f02000)
libpython3.6m.so.1.0 => /lib/libpython3.6m.so.1.0 (0xb7b83000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb7b64000)
libdl.so.2 => /lib/libdl.so.2 (0xb7b5f000)
libutil.so.1 => /lib/libutil.so.1 (0xb7b5b000)
libm.so.6 => /lib/libm.so.6 (0xb7a59000)
libc.so.6 => /lib/libc.so.6 (0xb78b5000)
/lib/ld-linux.so.2 (0xb7f04000)
$ cp -iv /lib/libpython3.6m.so* /lib/libutil.so* /lib/libm.so* /tmp/first_chroot/lib/ -iv
'/lib/libpython3.6m.so' -> '/tmp/first_chroot/lib/libpython3.6m.so'
'/lib/libpython3.6m.so.1.0' -> '/tmp/first_chroot/lib/libpython3.6m.so.1.0'
'/lib/libutil.so' -> '/tmp/first_chroot/lib/libutil.so'
'/lib/libutil.so.1' -> '/tmp/first_chroot/lib/libutil.so.1'
'/lib/libm.so' -> '/tmp/first_chroot/lib/libm.so'
'/lib/libm.so.6' -> '/tmp/first_chroot/lib/libm.so.6'
$ mkdir /tmp/first_chroot/usr/lib -p
$ cp /usr/lib/python3.6 /tmp/first_chroot/usr/lib/
$ cp -iv /lib/libz.so* /tmp/first_chroot/lib
Następnie możemy uruchomić naszą przykładową aplikację:
# /bin/python3.6 -m http.server 8998
Teraz możemy zobaczyć jaką korzyść niesie uruchomienie aplikacji w chroot. Załóżmy, że atakujący, wykorzystując błędy w aplikacji, przejął nad nią kontrolę i jest w stanie odczytać dowolne pliki z dysku. My na te potrzeby uruchomiliśmy serwer HTTP, który taką możliwość daje z założenia, ale efekt jest taki sam: klient łączący się do aplikacji ma dostęp do tych plików do których ma aplikacja. Łącząc się pod adres http://127.0.0.1:8898 widzimy, że aplikacja, a co za tym idzie atakujący ma dostęp jedynie do plików umieszczonych w chroot
$ curl http://127.0.0.1:8898
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ascii">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="bin/">bin/</a></li>
<li><a href="lib/">lib/</a></li>
<li><a href="tmp/">tmp/</a></li>
<li><a href="usr/">usr/</a></li>
</ul>
<hr>
</body>
</html>
Oznacza to, że w przypadku kompromitacji jednej aplikacji, nie następuje kompromitacja pozostałych uruchomionych tam aplikacji jak również samego systemu.
Zamykanie zdalnych użytkowników w chroot
Częstą praktyką jest również zamykanie zdalnych użytkowników w chroot-ach. Najłatwiej zrobić to poprzez utworzenie grupy użytkowników, a następnie dodawania kolejnych do tejże grupy.
$ groupadd chrooties
$ useradd chroot1 -g chrooties -M
$ passwd chroot1
Warto tutaj zwrócić uwagę na parametr -M, który mówi, aby useradd nie tworzył katalogu domowego - nie będzie nam on teraz potrzebny. W sytuacji w której będziemy chcieli logować się po kluczu, może się on okazać przydatny. Jednak w naszym przypadku zadowolimy się logowaniem hasłem.
Ważną rzeczą, którą trzeba tutaj zaznaczyć, są wymagania ssh co do uprawnień katalogu do którego będzie robiony chroot. Z przyczyn bezpieczeństwa, ssh wymaga, aby cała ścieżka do katalogu była w rękach root-a i tylko root-a. Dlatego musimy przenieść nasz first_chroot poza tmp oraz nadać mu odpowiednie uprawnienia.
$ mv /tmp/first_chroot/ /
# chown root:root /first_chroot/
# chmod 755 /first_chroot/
Teraz możemy skonfigurować ssh. W pliku /etc/ssh/sshd_config musimy dopisać sekcję dotyczącą naszych użytkowników
Match Group chrooties
ChrootDirectory /first_chroot
Po wykonaniu restartu, możemy się zalogować i wylistować katalogi
$ ssh chroot1@localhost
chroot1@localhost's password:
Last login: Sun Jan 6 08:25:41 2019 from 127.0.0.1
-bash-4.4$ /bin/ls
bin lib tmp usr
Widzimy, że użytkownik został zamknięty w przygotowanym chroot. Teraz jest już tylko w gestii administratora, co będzie posiadał w tym chroot.
Podsumowanie
Pokazaliśmy sobie czym jest chroot, jak go utworzyć, jak uruchomić w nim aplikację oraz zamknąć użyszkodników. Zachęcam do zadawania pytań oraz komentowania pod filmem na yt.