Dlaczego miałbym chroot dla piaskownicy dla bezpieczeństwa, jeśli moja aplikacja może od początku działać na niższym poziomie?

14

Piszę demona serwera HTTP w C (istnieją powody, dla których), zarządzając nim za pomocą pliku jednostki systemowej.

Piszę na nowo aplikację zaprojektowaną 20 lat temu, około 1995 roku. System, którego używają, to chroot, a następnie setuid, i standardowa procedura.

Teraz w mojej poprzedniej pracy normalną zasadą było to, że nigdy nie uruchamiasz żadnego procesu jako root. Tworzysz dla niego użytkownika / grupę i stamtąd biegasz. Oczywiście system działał jako root, ale moglibyśmy przetwarzać całą logikę biznesową bez rootowania.

Teraz dla demona HTTP mogę uruchomić go bez rootowania, jeśli nie będę chrootował się w aplikacji. Czy nie jest bezpieczniejsze, że aplikacja nigdy nie będzie działać jako root?

Czy nie jest bezpieczniej uruchomić go jako użytkownika mydaemon od samego początku? Zamiast zaczynać od roota, chrootować, a potem konfigurować dla mydaemon-user?

mur
źródło
3
musisz być uruchomiony jako root, aby korzystać z portu 80 lub 443. w przeciwnym razie możesz zrobić to, co robi tomcat i inne oprogramowanie web / serwer WWW i uruchomić na wyższym porcie (powiedzmy 8080, 9090 itp.), a następnie użyć albo apache / nginx, aby proxy nawiązać połączenie z oprogramowaniem serwera WWW lub użyj zapory systemowej, aby NAT / przekierować ruch do serwera WWW z portu 80. Jeśli nie potrzebujesz portu 80 lub 443 lub możesz proxy lub przekazać połączenie, wtedy nie musisz uruchamiać się jako root, w chroot lub w inny sposób.
SnakeDoc
3
@ SnakeDoc w systemie Linux już nie jest prawdą. Dzięki capabilities(7).
0xC0000022L
@SnakeDoc można użyć authbind także
Abdul Ahad

Odpowiedzi:

27

Wygląda na to, że inni pominęli twój punkt widzenia, co nie było powodem, dla którego warto używać zmienionych korzeni, o czym oczywiście już wiesz, ani co jeszcze możesz zrobić, aby ograniczyć granice demonów, skoro wiesz również o działaniu pod nadzorem nieuprzywilejowane konta użytkowników; ale po co to robić w aplikacji . W rzeczywistości istnieje dość trafny przykład, dlaczego.

Rozważ projekt httpdprogramu dæmon w pakiecie plików publicznych Daniela J. Bernsteina. Pierwszą rzeczą, którą robi, jest zmiana katalogu głównego na katalog główny, którego kazano mu użyć z argumentem polecenia, a następnie upuszczenie uprawnień do nieuprzywilejowanego identyfikatora użytkownika i grupy, które są przekazywane w dwóch zmiennych środowiskowych.

Zestawy narzędzi do zarządzania Dæmon mają dedykowane narzędzia do takich rzeczy, jak zmiana katalogu głównego i upuszczanie do nieuprzywilejowanych identyfikatorów użytkowników i grup. Runit Gerrita Pape'a ma chpst. Mój zestaw narzędzi Nosh ma chrooti setuidgid-fromenv. Laurent Bercot s6 ma s6-chrooti s6-setuidgid. Wayne Marshall's Perp ma runtooli runuid. I tak dalej. Rzeczywiście, wszystkie mają własny zestaw narzędzi Daemontools M. Bernsteina setuidgidjako poprzednik.

Można by pomyśleć, że można wyodrębnić funkcjonalność httpdi użyć takich dedykowanych narzędzi. Następnie, jak można sobie wyobrazić, żadna część programu serwera nigdy nie działa z uprawnieniami administratora.

Problem polega na tym, że jako bezpośrednia konsekwencja trzeba wykonać znacznie więcej pracy, aby skonfigurować zmieniony katalog główny, a to ujawnia nowe problemy.

W httpdobecnej postaci Bernstein jedynymi plikami i katalogami, które znajdują się w drzewie katalogów głównych, są te, które zostaną opublikowane na całym świecie. Na drzewie nie ma nic więcej . Co więcej, nie ma powodu, dla dowolny plik wykonywalny programu obraz istnieć w tym drzewie.

Ale przesunąć zmianę katalogu głównego na zewnątrz do programu łańcuch ładowania (lub Systemd) i nagle plik programu obraz httpd, wszelkich bibliotek, które ładuje, a jakieś specjalne pliki /etc, /runi /devże program ładujący lub C Access Runtime library podczas inicjalizacji programu (który może się okazać dość zaskakujące, jeśli truss/ straceC lub program C ++), również muszą być obecne w zmienionej korzenia. W przeciwnym razie httpdnie będzie można połączyć go w łańcuch i nie będzie się ładować / uruchamiać.

Pamiętaj, że jest to serwer treści HTTP (S). Może potencjalnie obsłużyć dowolny (czytelny na całym świecie) plik w zmienionym katalogu głównym. Obejmuje to teraz takie rzeczy, jak współdzielone biblioteki, program ładujący program oraz kopie różnych plików konfiguracyjnych modułu ładującego / CRTL dla systemu operacyjnego. A jeśli w jakiś (przypadkowy sposób) sposób serwer zawartości ma dostęp do zapisu , serwer, którego zabezpieczenia zostały naruszone, może uzyskać dostęp do zapisu obrazu programu dla httpdsiebie, a nawet programu ładującego system. (Pamiętaj, że masz teraz dwa równoległe zestawy /usr, /lib, /etc, /run, i /devkatalogi, aby zachować bezpieczny).

Nic z tego nie dzieje się, gdy httpdsame zmiany uprawnień roota i upuszczenia.

Wymieniłeś więc niewielką ilość uprzywilejowanego kodu, który jest dość łatwy do skontrolowania i który działa na początku httpdprogramu, działając z uprawnieniami administratora; za znacznie powiększoną powierzchnię ataku plików i katalogów w zmienionym katalogu głównym.

Dlatego nie jest to tak proste, jak robienie wszystkiego na zewnątrz programu serwisowego.

Zauważ, że jest to jednak absolutne minimum funkcjonalności httpdsamej w sobie. Cały kod, który wykonuje takie czynności, jak szukanie w bazie danych kont systemu operacyjnego identyfikatora użytkownika i identyfikatora grupy, aby umieścić je w tych zmiennych środowiskowych w pierwszej kolejności, jest zewnętrzny dla httpdprogramu, w prostych samodzielnych kontrolowanych komendach, takich jak envuidgid. (I oczywiście jest to narzędzie ucspi, więc zawiera żadnego kodu, aby nasłuchiwać na danym porcie TCP (-ych) lub do przyjmowania połączeń, te, które są domeną poleceń takich jak tcpserver, tcp-socket-listen, tcp-socket-accept, s6-tcpserver4-socketbinder, s6-tcpserver4d, i tak dalej).

Dalsza lektura

JdeBP
źródło
+1, winny jak oskarżony. Uznałem, że tytuł i ostatni akapit są niejednoznaczne, a jeśli masz rację, nie trafiłem w sedno. Ta odpowiedź daje bardzo praktyczną interpretację. Osobiście chciałbym wyraźnie zauważyć, że zbudowanie takiego środowiska chroot to dodatkowy wysiłek, którego większość ludzi chciałaby uniknąć. Ale 2 punkty bezpieczeństwa są już dobrze wykonane.
sourcejedi
Inną kwestią do zapamiętania jest to, że jeśli serwer upuści uprawnienia przed przetworzeniem jakiegokolwiek ruchu sieciowego, wówczas uprzywilejowany kod nie jest narażony na żadne zdalne ataki.
kasperd
5

Myślę, że wiele szczegółów twojego pytania może dotyczyć w równym stopniu avahi-daemon, na co ostatnio spojrzałem. (Mogłem przeoczyć inny szczegół, który się różni). Uruchamianie demona avahi w chroot ma wiele zalet, na wypadek, gdyby avahi-demon został naruszony. Obejmują one:

  1. nie może odczytać żadnego katalogu domowego użytkowników i wyodrębnić prywatnych informacji.
  2. nie może wykorzystywać błędów w innych programach, pisząc do / tmp. Istnieje co najmniej jedna cała kategoria takich błędów. Np. Https://www.google.co.uk/search?q=tmp+race+security+bug
  3. nie może otworzyć żadnego pliku gniazda UNIX, który znajduje się poza chroot, na którym inne demony mogą nasłuchiwać i czytać wiadomości.

Punkt 3 może być szczególnie przydatny, gdy nie używasz dbus lub podobnego ... Myślę, że avahi-demon używa dbus, więc zapewnia dostęp do systemu dbus nawet z chroota. Jeśli nie potrzebujesz możliwości wysyłania wiadomości w systemie dbus, odmowa tej możliwości może być całkiem niezłą funkcją bezpieczeństwa.

zarządzanie nim za pomocą pliku jednostki systemowej

Zauważ, że jeśli avahi-demon został ponownie napisany, może potencjalnie zdecydować się na systemd dla bezpieczeństwa i użyć np ProtectHome. Zaproponowałem zmianę w avahi-demona, aby dodać te zabezpieczenia jako dodatkową warstwę, wraz z kilkoma dodatkowymi zabezpieczeniami, które nie są gwarantowane przez chroot. Możesz zobaczyć pełną listę opcji, które zaproponowałem tutaj:

https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a

Wygląda na to, że istnieje więcej ograniczeń, których mógłbym użyć, gdyby avahi-demon nie używał samego chroota, niektóre z nich są wymienione w komunikacie zatwierdzenia. Nie jestem jednak pewien, ile to dotyczy.

Uwaga: zastosowane przeze mnie zabezpieczenia nie ograniczyłyby demona przed otwieraniem plików gniazd UNIX (punkt 3 powyżej).

Innym podejściem byłoby użycie SELinux. Jednak w pewnym sensie wiązałbyś swoją aplikację z tym podzbiorem dystrybucji Linuksa. Powodem, dla którego pomyślałem o SELinux tutaj, jest to, że SELinux w bardzo drobiazgowy sposób ogranicza dostęp procesów przez dbus. Na przykład myślę, że często można się spodziewać, systemdże nie będzie na liście nazw autobusów, do których trzeba było wysyłać wiadomości :-).

„Zastanawiałem się, czy korzystanie z piaskownicy systemowej jest bezpieczniejsze niż chroot / setuid / umask / ...”

Podsumowanie: dlaczego nie jedno i drugie? Rozszyfrujmy nieco powyższe :-).

Jeśli pomyślisz o punkcie 3, użycie chroot zapewnia większe zamknięcie. ProtectHome = i jego przyjaciele nawet nie starają się być tak restrykcyjni jak chroot. (Na przykład, żadna z nazwanych czarnych list systemowych opcji /run, w których zwykle umieszczamy pliki gniazd unix).

chroot pokazuje, że ograniczenie dostępu do systemu plików może być bardzo potężne, ale nie wszystko w Linuksie to plik :-). Istnieją systemowe opcje, które mogą ograniczać inne rzeczy, które nie są plikami. Jest to przydatne, jeśli program zostanie przejęty, możesz zmniejszyć dostępne dla niego funkcje jądra, w których może on próbować wykorzystać lukę. Na przykład demon avahi nie potrzebuje gniazd bluetooth i sądzę, że twój serwer sieciowy też nie :-). Nie udzielaj więc dostępu do rodziny adresów AF_BLUETOOTH. Wystarczy dodać do białej listy AF_INET, AF_INET6, a może AF_UNIX, używając tej RestrictAddressFamilies=opcji.

Przeczytaj dokumentację dotyczącą każdej opcji, której używasz. Niektóre opcje są bardziej skuteczne w połączeniu z innymi, a niektóre nie są dostępne we wszystkich architekturach procesora. (Nie dlatego, że procesor jest zły, ale dlatego, że port Linux dla tego procesora nie był tak ładnie zaprojektowany. Myślę, że).

(Jest tutaj ogólna zasada. Bardziej bezpieczne jest pisanie list tego, na co chcesz zezwolić, a nie tego, czego chcesz odmówić. Na przykład zdefiniowanie chroot daje listę plików, do których masz dostęp, a to jest bardziej niezawodne niż mówienie, że chcesz zablokować /home).

Zasadniczo możesz zastosować te same ograniczenia przed setuid (). To tylko kod, który można skopiować z systemd. Jednak opcje jednostek systemowych powinny być znacznie łatwiejsze do napisania, a ponieważ są w standardowym formacie, powinny być łatwiejsze do odczytania i przejrzenia.

Dlatego mogę gorąco polecić przeczytanie sekcji piaskownicy man systemd.execna Twojej platformie docelowej. Ale jeśli chcesz, aby możliwie najbardziej bezpieczny projekt, nie bój się próbować chroot(i następnie upuścić rootprzywileje) w programie , jak również . Tu jest kompromis. Używanie chrootnakłada pewne ograniczenia na ogólny projekt. Jeśli masz już projekt wykorzystujący chroot i wydaje się, że robi to, czego potrzebujesz, to brzmi całkiem nieźle.

sourcejedi
źródło
+1 szczególnie za sugestie systemowe.
mattdm
nauczyłem się sporo od twojej odpowiedzi, jeśli stos nad przepływem pozwala na wielokrotną odpowiedź, również przyjmę twoją. Zastanawiałem się, czy korzystanie z piaskownicy systemowej jest bezpieczniejsze niż chroot / setuid / umask / ...
mur
@mur cieszę się, że ci się podobało :). To bardzo naturalna odpowiedź na moją odpowiedź. Więc zaktualizowałem go ponownie, aby spróbować odpowiedzieć na twoje pytanie.
sourcejedi
1

Jeśli możesz polegać na systemd, to rzeczywiście bezpieczniej (i prościej!) Jest pozostawić sandboxing systemd. (Oczywiście aplikacja może również wykryć, czy została uruchomiona w trybie piaskownicy przez systemd, czy też sama piaskownica, jeśli nadal jest rootem.) Odpowiednik opisanej usługi to:

[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...

Ale nie musimy się na tym kończyć. systemd może również wykonać dla Ciebie wiele innych piaskownic - oto kilka przykładów:

[Service]
# allocate separate /tmp and /var/tmp for the service
PrivateTmp=yes
# mount / (except for some subdirectories) read-only
ProtectSystem=strict
# empty /home, /root
ProtectHome=yes
# disable setuid and other privilege escalation mechanisms
NoNewPrivileges=yes
# separate network namespace with only loopback device
PrivateNetwork=yes
# only unix domain sockets (no inet, inet6, netlink, …)
RestrictAddressFamilies=AF_UNIX

Zobacz man 5 systemd.execo wiele więcej dyrektyw i bardziej szczegółowe opisy. Jeśli man 5 systemd.socketsprawisz, że demon będzie aktywowany przez gniazdo ( ), możesz nawet użyć opcji związanych z siecią: jedynym łączem usługi do świata zewnętrznego będzie gniazdo sieciowe, które otrzymało od systemd, nie będzie mogło połączyć się z niczym innym. Jeśli jest to prosty serwer, który nasłuchuje tylko na niektórych portach i nie musi się łączyć z innymi serwerami, może to być przydatne. ( RootDirectoryMoim zdaniem opcje związane z systemem plików mogą również stać się przestarzałe, więc być może nie musisz już martwić się tworzeniem nowego katalogu głównego ze wszystkimi wymaganymi plikami binarnymi i bibliotekami.)

Obsługiwane są także nowsze wersje systemowe (od wersji 232) DynamicUser=yes, gdzie systemd automatycznie przydzieli użytkownika usługi tylko dla środowiska wykonawczego usługi. Oznacza to, że nie trzeba się rejestrować stałego użytkownika za usługę, a działa dobrze tak długo, jak służby nie pisać żadnych lokalizacjach systemu plików innych niż jego StateDirectory, LogsDirectoryi CacheDirectory(co można również zadeklarować w pliku jednostki - zobacz man 5 systemd.execjeszcze raz - i który system będzie wtedy zarządzał, dbając o prawidłowe przypisanie ich do użytkownika dynamicznego).

Lucas Werkmeister
źródło