...

Możliwości systemu Linux: bezpieczny i precyzyjny podział uprawnień administratora

Dzięki funkcji Linux Capabilities dzielę uprawnienia administratora na małe, jasno zdefiniowane przywileje, co pozwala mi radykalnie zmniejszyć ryzyko. W ten sposób precyzyjnie kontroluję, które procesy mogą wykonywać określone działania, i ograniczam powierzchnię ataku każdej aplikacji.

Punkty centralne

  • Drobnoziarnisty zamiast nadawać uprawnienia o nieograniczonym zakresie: podzielić uprawnienia administratora na mniejsze przywileje.
  • Możliwości plików zamiast Set-UID: przypisanie wymaganych uprawnień bezpośrednio do plików binarnych.
  • Zestawy możliwości konfiguracja: „Permitted”, „Effective”, „Inheritable”, „Bounding” – precyzyjna konfiguracja.
  • Rozdzielenie uprawnień: Należy ściśle rozdzielać usługi, narzędzia i zadania.
  • Obrona dogłębna: Uzupełnienie funkcji o sudo, role i protokoły.

Dlaczego warto oddzielić uprawnienia administratora?

Konto root zapewnia Pełny dostęp w zakresie plików i procesów, ale właśnie to sprzyja poważnym błędom. Wystarczy jedno błędne polecenie lub exploit, a cała instalacja ulegnie awarii. Dlatego ograniczam szeroko zakrojone działania do absolutnego minimum, zmniejszając w ten sposób skalę szkód i czas przywracania systemu. Zasada minimalnych uprawnień pozwala utrzymać usługi na niewielką skalę i pod kontrolą. Wyłączam bezpośrednie logowanie jako root, stosuję system ról i prowadzę kompletne logi.

Krótkie wyjaśnienie funkcji systemu Linux

Funkcje systemu Linux dzielą klasyczne uprawnienia administratora na jasno określone Przywileje. Każdy proces otrzymuje tylko te moduły, które są mu naprawdę potrzebne do wykonania zadania, na przykład do przypisywania portów poniżej 1024 lub wysyłania specjalnych sygnałów. W ten sposób unikam dawnego podejścia typu „wszystko albo nic”. Jądro zarządza tymi modułami dla każdego procesu i ściśle je egzekwuje. Dzięki temu kontrola pozostaje precyzyjna i przejrzysta.

Z technicznego punktu widzenia łączę umiejętności albo z Procesy (na podstawie ich zestawów kompetencji) lub do Pliki (jako atrybuty rozszerzone security.capability w plikach binarnych ELF). W przypadku execve()-Start jądro łączy uprawnienia pliku z zestawami uprawnień procesu: Mówiąc prościej, dozwolone uprawnienia z atrybutów pliku oraz uprawnienia dziedziczone przez proces wywołujący są łączone w nowy zestaw uprawnień dozwolonych (Permitted-Set) i – jeśli tak zaznaczono – jednocześnie aktywowane w zestawie uprawnień efektywnych (Effective-Set). Pozwala to uniknąć obejść związanych z Set-UID oraz zapewnia przejrzystość i możliwość weryfikacji uprawnień.

Zrozumienie zestawów zdolności w kontekście procesowym

Każdy proces posiada kilka zestawów uprawnień, które mogę celowo kontrola. Zbiór „Permitted-Set” określa, jakie uprawnienia proces może zasadniczo posiadać. Zbiór „Effective-Set” określa, które uprawnienia są aktualnie aktywne. Zbiór „Inheritable-Set” reguluje, jakie uprawnienia mogą być przekazywane procesom potomnym. Zbiór „Bounding-Set” wyznacza sztywny limit górny i zapobiega przekroczeniu go przez procesy.

Ambient Capabilities i Securebits

Oprócz znanych zestawów dostępny jest również Zestaw ambientowy, który w przypadku execve() nie wygasa automatycznie. Korzystam z niego, gdy proces bez uprawnień ma celowo minimalne uprawnienia w wielu wykonanie-ma zachowywać się pomijając te skoki (na przykład podczas wywoływania zewnętrznych programów pomocniczych). Uprawnienia ambientowe są uwzględniane w uprawnieniach efektywnych tylko wtedy, gdy wywoływany plik sam nie ustawia żadnych uprawnień pliku – w ten sposób zapobiegam niezamierzonej eskalacji.

Wraz z Securebits kontroluję szczegóły przejść, np. czy proces po zmianie identyfikatora UID może zachować swoje wcześniej ustawione uprawnienia (keepcaps) lub czy w ogóle nie może uzyskać żadnych nowych przywilejów (no_new_privs). W praktyce stosuję Securebits w sposób rygorystyczny i rezygnuję z wygody, aby przerwać łańcuchy exploitów.

Możliwości plików zamiast Set-UID

Zastępuję pliki binarne z atrybutem Set-UID przez uprawnienia plików, aby zminimalizować ryzyko obniżać. Zamiast nadawać programowi uprawnienia administratora, przyznaję mu tylko niezbędne uprawnienia. Typowa zmiana wygląda następująco: setcap 'cap_net_bind_service=+ep' /usr/bin/meinserver. Z getcap -r / Sprawdzam, które pliki zawierają informacje o umiejętnościach. To znacznie ogranicza liczbę kolejnych kroków w procesie eskalacji.

Ważne jest, aby funkcje plików były stosowane wyłącznie w odniesieniu do Pliki binarne ELF działają. Skrypty interpretera (np. Python, Bash) nie dziedziczą ich w sposób niezawodny. W takich przypadkach umieszczam operację wymagającą uprawnień w małym, statycznie sprawdzonym programie pomocniczym lub korzystam z aktywacji przez gniazdo (socket activation), aby moja usługa w ogóle nie musiała sama nawiązywać połączenia. Ponadto zwracam uwagę na uprawnienia plików: możliwości (capabilities) przyznają specjalne uprawnienia względem jądra, ale zastępują brak standardowych list ACL lub uprawnień POSIX.

Podczas kopiowania lub pakowania umiejętności szybko się tracą: cp bez obsługi XATTR, nieprawidłowo ustawione umask lub usunąć artefakt kompilacji z systemu plików bez atrybutów rozszerzonych security.capability w sposób dorozumiany. Dlatego pracuję w sposób powtarzalny i korzystam z: cp --preserve=xattr ..., tar --xattrs, rsync -X. W przypadku kompilacji pakietów jawnie ustawiam możliwości plików w skrypcie instalacyjnym, testuję instalację na czystej maszynie wirtualnej i sprawdzam getcap w CI.

Rozdzielenie uprawnień w oparciu o realistyczne scenariusze

Serwer WWW potrzebuje dostępu do portów 80/443, ale nie do modułów jądra ani do ponownego uruchamiania systemu, dlatego ustawiam CAP_NET_BIND_SERVICE i nic więcej. Agent kopii zapasowej może odczytywać i zapisywać pliki, ale nie może zmieniać konfiguracji sieciowej. Narzędzie monitorujące otrzymuje uprawnienia do odczytu wskaźników, ale nie ma uprawnień do wprowadzania zmian. Takie ograniczenia sprawiają, że ataki mają charakter lokalny, a nie obejmują całego systemu. Właśnie ten podział sprawia, że usługi są łatwe w zarządzaniu, a błędy konfiguracyjne pozostają pod kontrolą.

Łączenie z poleceniem `sudo` i rolami

Funkcje nie zastępują przejrzystego Struktura ról, a raczej je uzupełniają. Przyznaję uprawnienia sudo w sposób restrykcyjny, stosuję pełne ścieżki poleceń i unikam ogólnych reguł, takich jak „ALL=(ALL) ALL“. Każde przyznanie uprawnień rejestruję. Grupy skupiają zakresy odpowiedzialności, podczas gdy uprawnienia (capabilities) wyznaczają techniczne ograniczenia w procesach. W ten sposób powstają jasno określone kompetencje bez nadmiernych uprawnień.

Typowe pułapki i najlepsze praktyki

  • Nie używać skrótu CAP_SYS_ADMIN: To prawo jest zbyt ogólne. Zastępuję je bardziej szczegółowymi alternatywami (np. CAP_SYS_CHROOT, CAP_SYS_TIME, CAP_SYS_NICE) albo w ogóle z tego zrezygnuj.
  • Uprawnienia do plików pozostają niezmienne: Możliwości nie wykluczają ogólnie DAC. Bez CAP_DAC_OVERRIDE Jądro nadal uwzględnia bity właściciela i trybu dostępu. Dlatego nadal przyznaję uprawnienia do odczytu w minimalnym zakresie.
  • Utwardzanie ścieżki: Jeśli nadam plikowi binarnemu uprawnienia plików, zapobiegnę sfałszowaniu ścieżki PATH (ścieżki bezwzględne w sudoers, zablokowane uprawnienia do zapisu w katalogach znajdujących się w ścieżce wyszukiwania).
  • Publikuj wcześnie i często: Procesy mogą uruchamiać się z większymi uprawnieniami niż to konieczne. Usuwam zbędne uprawnienia natychmiast po wykonaniu newralgicznego kroku (prctl()/libcap) i ustaw no_new_privs, tam, gdzie to możliwe.
  • Ograniczenie dziedziczenia: Utrzymuję zestaw Inheritable i Ambient na niewielkim poziomie. Procesy potomne nie mogą otwierać nowych drzwi.
  • Sprawdź potok kompilacji i wdrażania: Potwierdzam, że security.capability zostaje zachowany i żadne etapy przygotowania (warstwy kontenerów, NFS, skaner artefaktów) nie usuwają atrybutów XATTR.

Przegląd kluczowych możliwości i zagrożeń

Przed przyznaniem uprawnień jasno określę niezbędne uprawnienia i oceniam związane z nimi ryzyko. Poniższa tabela przedstawia typowe przykłady wraz z ich skutkami i klasyfikacją. Zawsze biorę pod uwagę alternatywne rozwiązania, aby uniknąć przyznawania zbyt szerokich uprawnień. Zwłaszcza CAP_SYS_ADMIN Przyznaję je z dużą ostrożnością. Tam, gdzie to możliwe, zastępuję szeroko zakrojone uprawnienia ukierunkowanymi, wąsko zdefiniowanymi wariantami.

Możliwości Cel Ryzyko Przykład
CAP_NET_BIND_SERVICE Przypisywanie portów poniżej 1024 Niski do średniego Serwer WWW na portach 80/443
CAP_SYS_BOOT Uruchom ponownie system Wysoki Planowane ponowne uruchomienie
CAP_SYS_MODULE Ładowanie/usuwanie modułów jądra Bardzo wysoki Zarządzanie sterownikami
CAP_SYS_ADMIN Wszechstronne operacje administracyjne Bardzo wysoki Różne zadania konserwacyjne
CAP_SETUID / CAP_SETGID Zmiana UID/GID Średni do wysokiego Zmiana uprawnień w trakcie służby

Oprócz tabeli oceniam właśnie CAP_SYS_PTRACE (debugowanie procesów), CAP_NET_ADMIN (parametryzacja sieci) oraz CAP_DAC_OVERRIDE (obchodzenie ograniczeń dostępu do plików) jest kwestią niezwykle krytyczną. Często istnieją wzorce, które pozwalają uniknąć tych uprawnień: dedykowane punkty końcowe metryki zamiast podglądu procesów, aktywacja gniazd lub przekierowania portów zamiast uprawnień typu „bind” oraz przejrzyste uprawnienia do plików zamiast ogólnego obchodzenia DAC.

Wzmocnienie w kontenerach i hosting

W środowiskach wielodostępnych uważam, że umiejętności są radykalnie mały i zapobiegam dziedziczeniu w procesach potomnych. Kontenery odnoszą znaczne korzyści, gdy tylko zbiór granic (bounding set) jest ściśle określony. Łączę to z izolowanymi przestrzeniami systemu plików i procesów. W uzyskaniu ogólnego obrazu podejść do izolacji pomaga mi ten wprowadzenie do Izolacja procesów. Dzięki temu usługi pozostają oddzielone, nawet jeśli jedna z aplikacji zacznie działać nieprawidłowo.

W praktyce domyślnie ustawiam kontenery na opcję „wyrzuć wszystko, dodaj wybrane“: --cap-drop=ALL --cap-add=NET_BIND_SERVICE dla usług internetowych, brak uprawnień do montowania, brak SYS_ADMIN. W środowiskach orkiestrowanych przechowuję profil centralnie i sprawdzam go w zasadach. Ważne: nie polegam na uprawnieniach plików w obrazie, lecz przyznaję uprawnienia w czasie wykonywania w orkiestratorze – w sposób powtarzalny i podlegający audytowi.

Współdziałanie z SELinux i AppArmor

Możliwości określają, co proces może robić, podczas gdy profile MAC określają, do czego ma on dostęp, a oba te elementy współgrają ze sobą dobry. Stosuję wąskie ustawienia uprawnień (Capabilities) i pozwalam SELinuxowi lub AppArmorowi ograniczać dostęp do plików i gniazd. W ten sposób powstaje wielowarstwowa ochrona, która stawia przed exploitami szereg przeszkód. Szybkie porównanie znajduję tutaj: SELinux a AppArmor. Dzięki temu usługa, której bezpieczeństwo zostało naruszone, pozostaje odizolowana i może wyrządzić mniej szkód.

W praktyce: postępuj krok po kroku

Zacznę od sporządzenia wykazu wszystkich usług i ich Wymagania. Następnie usuwam zbędne pliki binarne z atrybutem Set-UID lub zastępuję je odpowiednimi uprawnieniami plików. Konfiguruję sudo w sposób restrykcyjny i dokumentuję każdy wpis. Przypisuję zadania do ról i grup, ograniczając uprawnienia do minimum. Następnie przeprowadzam testy pod obciążeniem i sprawdzam wpisy w logach pod kątem nieoczekiwanych odmów.

Krótka lista kontrolna pomaga mi w zmianie:

  • Należy na piśmie określić wymagania dla poszczególnych stanowisk (tylko to, co jest naprawdę potrzebne).
  • Sporządzenie wykazu istniejących przywilejów (find / -perm -4000, getcap -r /).
  • Celowe zastępowanie: rezygnacja z atrybutu Set-UID, ustawianie uprawnień plików, wczesne cofanie uprawnień.
  • Zamknięcie dziedziczenia: zoptymalizowanie zbioru ograniczającego, zminimalizowanie elementów dziedzicznych/środowiskowych.
  • Zabezpieczenie profili systemd i kontenerów (CapabilityBoundingSet=, NoNewPrivileges=yes).
  • Przeprowadzić testy pod obciążeniem, sprawdzić logi i wpisy audytowe, udokumentować wyjątki.

Monitorowanie, przestrzenie nazw i ciągłe audyty

Monitoruję pliki dziennika, alarmy i wywołania systemowe, aby natychmiast reagować na niepożądane działania wyróżniać się. Regularnie weryfikuję zmiany w uprawnieniach, regułach sudo i rolach. Tam, gdzie jest to uzasadnione, dodatkowo izoluję obciążenia za pomocą mechanizmów izolacji jądra. Dobrym punktem wyjścia jest ten przegląd dotyczący Przestrzenie nazw i grupy Cgroup. Dzięki temu wcześnie wykrywam nieprawidłowości i dbam o czystość otoczenia.

Na co dzień stosuję proste testy: capsh --print wyświetla mi aktualny zestaw umiejętności, getpcaps wymienia prawa procesowe oraz w /proc//status czytam CapEff, CapPrm, CapBnd. Z audyt śledzę zmiany statusu zdolności (np. reguła na capset), koreluję zdarzenia z wdrożeniami i ustawiam alerty, gdy nagle pojawiają się szerokie uprawnienia. W skomplikowanych przypadkach pomaga mi strace -e capget,capset, aby uwidocznić manipulacje uprawnieniami.

Praktyczne przykłady dotyczące systemd i kontenerów

Wiele usług uruchamiam jako jednostki systemd i tam ograniczam uprawnienia:

  • CapabilityBoundingSet=CAP_NET_BIND_SERVICE ogranicza dostępne okno praw do niezbędnego minimum.
  • AmbientCapabilities=CAP_NET_BIND_SERVICE nadaje usłudze prawo do nawiązania połączenia na portach 80/443 bez konieczności określania możliwości plików.
  • NoNewPrivileges=yes uniemożliwia późniejsze rozszerzenie zakresu praw.
  • Użytkownik=, Grupa=, ProtectSystem=ostry, PrivateTmp=yes dopełniają izolację.

W kontenerach uruchamiam procesy w jak najmniejszym zakresie: docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE --read-only. W przypadku zadań krótkotrwałych korzystam w obrazie z funkcji związanych z czasem działania zamiast funkcji związanych z plikami, aby kompilacje były powtarzalne, a uprawnienia były powiązane ze środowiskiem.

Konkretne przykłady migracji z praktyki

  • ping bez set-UID: Zamiast setuid root stawiam setcap 'cap_net_raw=+ep' /bin/ping. Dzięki temu każdy użytkownik może otwierać gniazda ICMP bez pełnych uprawnień administratora. Regularnie sprawdzam to za pomocą getcap /bin/ping, czy atrybut został zachowany.
  • Usługa internetowa na porcie 80/443: Uruchamiam usługę jako użytkownik bez uprawnień i podaję tylko cap_net_bind_service. Jeśli usługa i tak znajduje się za serwerem proxy odwrotnym, mogę alternatywnie podłączyć się tam do portów 80/443 i korzystać wewnętrznie z portu wysokiego – zupełnie bez dodatkowych umiejętności.
  • Zmiana stron w postępowaniu: W przypadku narzędzi, które na krótko wymagają podwyższonych uprawnień (np. ustawianie poziomów priorytetu), ustawiam cap_sys_nice, wykonaj akcję jak najwcześniej, a potem zrezygnuj z tej umiejętności. Staram się unikać trwałego zwiększania uprawnień.

Ograniczenia i alternatywy

Nie każdy przypadek zastosowania wymaga wykorzystania funkcji. Często istnieją bezpieczne alternatywy o mniejszym ryzyku:

  • Aktywacja gniazda: Usługa inicjująca (np. systemd) otwiera gniazda z uprawnieniami i przekazuje je procesowi. Dzięki temu moja usługa nie potrzebuje uprawnień do wiązania.
  • Przekierowanie portów: Za pomocą reguł zapory sieciowej przekierowuję porty 80/443 na port o wyższym numerze. Usługa pozostaje bez uprawnień, a zachowanie systemu nie ulega zmianie.
  • Nieuprawnione porty niskiego poziomu: Tam, gdzie to ma sens, mogę podnieść próg dla portów bez uprawnień. Zwiększa to jednak zakres swobody dla wszystkich procesów – starannie rozważam ryzyko i wygodę.
  • Małe pomocniki zamiast uniwersalnych urządzeń: Wolę niewielki, sprawdzony plik binarny z dokładnie jedną funkcją niż wielki monolit z szerokim zestawem uprawnień.

Krótkie podsumowanie

Z Możliwości systemu Linux Dzielę uprawnienia administratora na małe, łatwe do kontrolowania przywileje. Możliwości plików zastępują ryzykowne pliki binarne z atrybutem Set-UID i ograniczają skutki ataku. W połączeniu z rygorystycznymi regułami sudo, rolami i profilami MAC powstaje wielowarstwowa ochrona o jasno określonych granicach. Zbiory ograniczające (Bounding Sets) i dziedziczne (Inheritable Sets) ograniczają dziedziczenie uprawnień i utrzymują procesy na właściwym kursie. Takie podejście znacznie zmniejsza powierzchnię ataku i pozwala utrzymać nakład administracyjny na rozsądnym poziomie.

Artykuły bieżące