...

Uprawnienia systemu Linux zamiast uprawnień roota: zasada minimalności w usługach serwerowych

Ustawiłem możliwości systemu Linux w celu prowadzenia usług serwerowych zgodnie z zasadą minimalności i przyznawania w ten sposób wyłącznie absolutnie niezbędnych uprawnień częściowych. W ten sposób ograniczam Powierzchnia ataku wyraźnie odczuwalne, bez blokowania funkcji.

Punkty centralne

  • Najmniejszy przywilej konsekwentnie: usługi otrzymują wyłącznie te funkcje, które są faktycznie potrzebne.
  • Drobnoziarnisty zamiast uprawnień roota: około 40–50 uprawnień zastępuje pełny dostęp.
  • Separacja w zakresie procesów: rozdzielenie uprawnień ogranicza szkody wynikające z wykorzystania luk w zabezpieczeniach.
  • Plik Możliwości: Powiązanie uprawnień bezpośrednio z plikami binarnymi.
  • Możliwość przeprowadzenia audytu: getcap zapewnia przejrzysty obraz uprawnień specjalnych.

Dlaczego uzyskanie uprawnień roota wiąże się z ryzykiem – i jak zmienia to mechanizm „Capabilities”

Kiedyś prawie każda usługa serwerowa działała na Prawa korzeniowe, co w przypadku naruszenia bezpieczeństwa mogło natychmiast doprowadzić do przejęcia kontroli nad systemem. Obecnie celowo rozdzielam uprawnienia, wykorzystując funkcje takie jak CAP_NET_BIND_SERVICE przyznaję uprawnienia dla portów poniżej 1024 i usuwam wszystkie pozostałe szerokie uprawnienia. Dzięki temu serwer WWW może nawiązywać połączenia, ale nie może ładować modułów jądra ani zmieniać właścicieli plików, co Bezpieczeństwo znacznie zwiększa. Wyraźny podział zadań sprawia, że ataki są mniej skuteczne, ponieważ proces, który został przejęty, może wykonywać jedynie ograniczone czynności. Kto chce nadać tej koncepcji bardziej uporządkowany charakter, może bardzo precyzyjnie definiować uprawnienia podzielić na mniejsze części i w ten sposób systematycznie ograniczać operacje krytyczne. W ten sposób z monolitycznej usługi głównej powstaje zestaw usług o minimalnych, jasno określonych uprawnieniach.

Tak działają zestawy zdolności w jądrze systemu

Każdy proces składa się z kilku Zestawy możliwości, które jądro sprawdza podczas wykonywania wrażliwych operacji. Zestaw efektywny (Effective-Set) określa, co proces może zrobić w danej chwili, natomiast zestaw dozwolony (Permitted-Set) zawiera pulę możliwych uprawnień. Za pomocą zestawu dziedziczalnego (Inheritable-Set) mogę kontrolować, co podczas execve() przekazuje do procesów potomnych, co ma szczególne znaczenie w przypadku opakowań i skryptów startowych. Zbiór ograniczający (Bounding-Set) określa sztywny limit górny, dzięki czemu określone uprawnienia nie mogą zostać ponownie uzyskane, nawet w przypadku błędów w aplikacji. Za pomocą zbioru otoczenia (Ambient-Set) przekazuję uprawnienia bez SUID do zwykłych programów i zachowuję Trasa ataku małe. Połączenie tych zestawów pozwala mi na bardzo precyzyjne sterowanie, które znacznie wykracza poza klasyczne podejście „wszystko albo nic” charakterystyczne dla UID 0.

Zestaw Cel Typowe zastosowanie Ryzyko błędnej konfiguracji
Skuteczny Umiejętności aktywne teraz Sprawdzenie każdej operacji uprzywilejowanej Proces może od razu przynieść zbyt wiele
Dozwolone Zbiór dozwolonych umiejętności Źródło zestawu efektywnego Niepotrzebne rezerwy pozostają w zasięgu ręki
Dziedziczne Umiejętności dziedziczne Kontrolowane przekazywanie w funkcji execve() Dzieci dziedziczą prawa niepotrzebnie
Ograniczanie Górna granica wszystkich praw Zdefiniowanie trwałych wykluczeń Możliwe jest odzyskanie szerokich uprawnień
Ambient Udostępnianie bez uprawnień SUID Programy standardowe otrzymują uprawnienia Szersze, ciche przyznawanie praw

W praktyce istotne są dwa dodatkowe aspekty: po pierwsze, decydujące znaczenie ma Securebits o tym, czy proces po zmianie identyfikatora użytkownika (np. poprzez setuid()) zachowuje swoje możliwości. Dzięki PR_SET_KEEPCAPS można to celowo kontrolować – typowy przebieg: krótkotrwałe uruchomienie jako root, utworzenie niezbędnych gniazd lub zasobów, zmiana identyfikatora UID na użytkownika bez uprawnień i kontynuowanie działania wyłącznie z niezbędnymi uprawnieniami. Po drugie: to Zbiór ograniczający znajduje się ostatecznie w bieżącej ścieżce procesowej. Kto na wczesnym etapie ścieżki startowej usunie zbędne uprawnienia, nie będzie mógł później uzyskać „niedozwolonych“ uprawnień, nawet w wyniku błędnej konfiguracji.

Zarządzanie uprawnieniami do plików za pomocą funkcji File Capabilities

Zamiast jednorazowej usługi – trwałe Prawa specjalne Zamiast to uwzględniać, wolę połączyć je bezpośrednio z plikiem binarnym. Poprzez setcap cap_net_bind_service=+eip /usr/bin/node zezwalam na powiązania portów bez konieczności uruchamiania procesu jako root. Dzięki getcap /usr/bin/node lub rekurencyjnie getcap -r / 2>/dev/null sprawdzam przydziały i zachowuję kontrolę. Usunięcie odbywa się poprzez setcap -r /ścieżka/do/pliku/binarnego, dlatego po zakończeniu zadania cofam tymczasowe uprawnienia. Podczas kopiowania często dochodzi do utraty uprawnień, dlatego podczas wdrażania wyraźnie je zabezpieczam, aby regresja aby tego uniknąć. Dzięki temu kompilacje są powtarzalne, a uprawnienia są zawsze udokumentowane w sposób umożliwiający ich prześledzenie.

Możliwości plików są realizowane jako atrybuty rozszerzone (security.capability) w systemie plików. Wymaga to odpowiedniego systemu plików oraz właściwych opcji montowania. Narzędzia takie jak smoła oraz rsync należy jawnie uwzględnić atrybuty XAttr (np. tar --xattrs, rsync -XA), w przeciwnym razie uprawnienia znikną bez ostrzeżenia. Menedżery pakietów mogą ustawiać uprawnienia w etapach po instalacji; ja wolę to ustalać w procesie kompilacji/wydania, aby uniknąć niespodzianek podczas aktualizacji. Kolejna istotna kwestia: skrypty interpretowane (np. z shebangiem) nie dziedziczą uprawnień plików tak jak pliki binarne ELF. Potężne uprawnienia na tłumacz Ustawianie tego jest i tak ryzykowne – wolę odłączyć to i pracować z małymi, dedykowanymi plikami binarnymi.

Zasada minimalizmu w praktyce w przypadku usług serwerowych

Uruchamiam serwer WWW jako użytkownik bez uprawnień i przyznaję wyłącznie CAP_NET_BIND_SERVICE, aby proces mógł się powiązać z portem 80/443 i nie było żadnych innych Przywileje . Pliki i katalogi nadal zarządzam za pomocą uprawnień POSIX oraz, opcjonalnie, profili MAC, dzięki czemu konfiguracja i zawartość pozostają oddzielnie zabezpieczone. Agenci monitorujący lub rejestrujący otrzymują określone uprawnienia sieciowe oraz uprawnienia do odczytu logów, ale nie mają żadnych uprawnień do wprowadzania zmian w systemie. W środowiskach kontenerowych dodatkowo ograniczam zestaw uprawnień (capabilities) i łączę go z filtrami wywołań systemowych, aby ściśle kontrolować zachowanie. Takie połączenie zmniejsza skutki udanych ataków i zwiększa Przejrzystość rzeczywistych uprawnień. Służby nadal funkcjonują, jednak pole manewru pozostaje niewielkie.

Zamiast przyznawać uprawnienia, czasami całkowicie je eliminuję: aktywacja gniazda udostępnia uprzywilejowane punkty nasłuchiwania (np. 443/tcp) za pośrednictwem procesu Init i przekazuje usłudze jedynie otwarty deskryptor pliku. Proces aplikacji nie potrzebuje wówczas żadnego CAP_NET_BIND_SERVICE więcej. Podobnie można najpierw wykonać jednorazowe czynności wymagające uprawnień roota (np. utworzenie katalogu PID), a następnie konsekwentnie zrzec się tych uprawnień. Im mniej uprawnień w ogóle im więcej elementów jest w grze, tym bardziej odporny jest system na błędy łańcuchowe.

Prawidłowe wdrożenie separacji uprawnień

Rozdzielam obszerne usługi na kilka Procesy cząstkowe, z których każdy posiada wyłącznie niezbędne uprawnienia. Proces frontendowy nawiązuje połączenia TLS i obsługuje porty, ale nie posiada uprawnień do systemu plików umożliwiających wprowadzanie krytycznych zmian. Proces zaplecza przetwarza dane wewnętrznie, ma minimalne uprawnienia do odczytu konfiguracji i komunikuje się z bazami danych bez własnych możliwości sieciowych. Zadania administracyjne, takie jak rotacja logów lub konserwacja, są wykonywane za pomocą dedykowanych narzędzi o ograniczonych czasowo uprawnieniach. Jeśli atakujący zaatakuje jedną część systemu, reszta pozostaje nienaruszona, ponieważ Zezwolenia są ściśle zdefiniowane. W ten sposób bezpieczeństwo dostosowuje się do struktury aplikacji, a nie opiera się na wszechstronnych uprawnieniach systemowych.

W przypadku takiego podziału warto zastosować przejrzystą koordynację uruchamiania. W klasycznych konfiguracjach zadanie to pełni program Supervisor; w dzisiejszych systemach preferuję systemd, ponieważ bezpośrednio integruje on uprawnienia (capabilities), grupy cgroup oraz przestrzenie nazw (namespaces). Dzięki temu mogę uruchamiać interfejs sieciowy, procesy robocze i narzędzia administracyjne w oddzielnych środowiskach izolowanych, ograniczać zasoby oraz w razie awarii automatycznie je ponownie uruchamiać – bez konieczności przyznawania ogólnych uprawnień roota.

Łączenie mechanizmów zabezpieczeń: POSIX, MAC i uprawnienia

Funkcje działają najlepiej, gdy połączę je z klasycznymi Uprawnienia do plików i systemach MAC. SELinux lub AppArmor mogą dodatkowo ograniczać działania pomimo przyznanych uprawnień, tworząc w ten sposób wielopoziomową ochronę. W ten sposób proces może wprawdzie nawiązać połączenie z portem, ale polityka uniemożliwia mu odczyt wrażliwych plików. Kto chce zgłębić różnice między tymi podejściami, znajdzie przejrzyste porównanie w SELinux a AppArmor i może następnie wybrać odpowiednią strategię polityki bezpieczeństwa. W rezultacie powstaje system obrony, który powstrzymuje ataki na wielu poziomach i Powierzchnia ataku dalsze ograniczenie. Dzięki temu przyznawanie uprawnień pozostaje możliwe do zweryfikowania, powtarzalne i spójne.

Sytuacja staje się szczególnie restrykcyjna, gdy dodatkowo NoNewPrivileges Włącz: procesy i procesy potomne nie będą mogły wówczas uzyskać nowych uprawnień (np. poprzez SUID lub nowo ustawione możliwości plików). W połączeniu ze ścisłą listą ograniczeń możliwości tworzy to barierę bezpieczeństwa, która nawet w przypadku błędnej konfiguracji zapobiega późniejszemu rozszerzeniu uprawnień.

Bezpieczne przydzielanie uprawnień i ich audytowanie

Uważam, że przydzielony Zestaw umiejętności jak najmniejsze i unikaj wszystkiego, co brzmi jak „drugi root“, na przykład CAP_SYS_ADMIN. Interpretery, takie jak Python, Perl czy powłoki, nie otrzymują zaawansowanych uprawnień, ponieważ ich zakres funkcji można łatwo nadużyć. Dzięki regularnym audytom przeprowadzanym za pośrednictwem getcap -r / 2>/dev/null wykrywam nieprawidłowości i je usuwam. Pliki binarne z uprawnieniami są chronione przed zapisem, należą do użytkownika root i nie znajdują się w ścieżkach, które zwykli użytkownicy mogą modyfikować. Dodatkowo przed każdym wydaniem sprawdzam własne pliki binarne i dokumentuję zmiany, aby Recenzja a reprodukcja przebiega niezawodnie. Dzięki temu przyznawanie praw pozostaje pod kontrolą, a wprowadzane zmiany są przejrzyste.

W czasie wykonywania sprawdzam procesy za pomocą /proc//status (Pola CapEff, CapPrm, CapInh). Dostarcza to wartości szesnastkowe aktywnych zestawów i od razu pokazuje, czy dana aplikacja ma większe możliwości niż przewidziano. Narzędzia takie jak capsh --print lub getpcaps ułatwiają debugowanie. Za pomocą podsystemu audytowego Linuksa rejestruję dodatkowo zmiany w uprawnieniach (capabilities) lub w security.capability-atrybutów plików w celu śledzenia manipulacji. Kto traktuje uprawnienia jako obiekt konfiguracyjny i rygorystycznie weryfikuje zmiany, zapewnia powtarzalność audytów i ułatwia wykazywanie zgodności z przepisami.

Częste pułapki i jak ich unikać

Typowa pułapka: podczas kopiowania Atrybuty zostały utracone, przez co usługi nagle przestają się uruchamiać lub, przeciwnie, nie są wystarczająco ograniczone. Dlatego wyraźnie zabezpieczam uprawnienia w kompilacji lub przypisuję je automatycznie na etapie po instalacji. Kolejnym błędem jest nadmierne stosowanie uniwersalnych uprawnień, które otwierają więcej możliwości niż to konieczne. Lepiej jest stosować konkretne uprawnienia, takie jak CAP_NET_RAW lub CAP_CHOWN stosować je tylko tam, gdzie faktycznie pełnią jakąś funkcję. Również zestaw ambientowy stosuję oszczędnie, aby nie powstały niepożądane Przekazywanie powszechne. Kto celowo ogranicza i regularnie sprawdza, zapobiega lukom w bezpieczeństwie wynikającym z błędów obsługi.

Równie ważne: systematyczne wycofywanie plików binarnych z uprawnieniami SUID. Tam, gdzie wcześniej uprawnienia SUID były niezbędne (np. do wysyłania pakietów ICMP), często można to osiągnąć za pomocą CAP_NET_RAW pracować – lub, jeszcze lepiej, przenieść tę funkcję do jak najmniejszej możliwej podprogramu o bardzo wąskim zakresie zadań. Ponadto unikam umieszczania uprawnień w ścieżkach tymczasowych lub takich, do których użytkownicy mają uprawnienia zapisu. Rygorystyczny system zarządzania właścicielami i wdrażaniem (Root:root, 0755/0555, niezmienne ścieżki) zapobiega „utracie“ uprawnień w wyniku zastąpienia plików binarnych.

Możliwości w zakresie kontenerów i DevSecOps

W środowiskach kontenerowych ograniczam Możliwości agresywnie i usuwam wszystko, co nie jest absolutnie niezbędne dla obciążenia. Dodatkowo tworzę Profil Seccomp który blokuje ryzykowne wywołania systemowe, wprowadzając w ten sposób kolejną barierę. W potokach kompilacji definiuję uprawnienia w sposób deklaratywny, testuję je w środowisku stagingowym i rejestruję wraz z numerami wersji. Przynosi to korzyści w zakresie zgodności z przepisami, ponieważ mogę wykazać przestrzeganie zasady minimalnych uprawnień oraz kompleksowo dokumentować zmiany uprawnień. W ten sposób kontenery pozostają ściśle kontrolowane, bez utrudniania wykonywania ich zadań, a Powierzchnia ataku pozostaje niewielki. W połączeniu z obrazami zawierającymi tylko niezbędne elementy bezpieczeństwo dodatkowo wzrasta.

Ważna informacja w kontekście kontenerów: funkcje znajdują się w przestrzeniach nazw Względny. W obrębie przestrzeni nazw użytkownika proces może wprawdzie posiadać uprawnienia „root“, ale jego uprawnienia mają wpływ jedynie na powiązane przestrzenie nazw – co znacznie ogranicza zasięg szkód. Z drugiej strony „--z uprawnieniami“praktycznie zawsze jest to zabronione: wyłącza to ścisłą granicę wyznaczania obszaru i otwiera znacznie więcej niż to konieczne. Dlatego domyślnie uruchamiam kontenery z opcją „wszystko upuścić, dodawać selektywnie“ i uzupełniam NoNewPrivileges, limity cgroup oraz montowania w trybie tylko do odczytu. W przypadku usług, które muszą jedynie nasłuchiwać, korzystam z aktywacji gniazd lub modułów typu sidecar, aby całkowicie uniknąć stosowania dodatkowych uprawnień.

Przykład dotyczący systemd: deklaratywne ograniczanie uprawnień

W jednostkach serwisowych określam, jakie są maksymalne dopuszczalne parametry procesu – w sposób jasny, powtarzalny i podlegający kontroli wersji. Oto zwięzły przykład usługi internetowej, która może łączyć się wyłącznie z portem 443 i poza tym podlega znacznym ograniczeniom:

[Unit]
Description=Minimalna usługa internetowa bez uprawnień roota

[Service]
User=web
Group=web
ExecStart=/usr/bin/my-web
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/my-web
RestrictAddressFamilies=AF_INET AF_INET6
SystemCallFilter=@basic-io @network-io
LockPersonality=yes
MemoryDenyWriteExecute=yes

[Install]
WantedBy=multi-user.target

Połączenie Możliwości środowiskowe i trudnej Zbiór graniczny zdolności zapewnia, że usługa otrzymuje wyłącznie niezbędną kompetencję i nic ponadto. NoNewPrivileges uniemożliwia późniejsze podwyższenie uprawnień, ProtectSystem oraz ReadWritePaths regulują dostęp do zapisu, a ścisły filtr wywołań systemowych zapobiega niepotrzebnym punktom wejścia do jądra.

Często wykorzystywane funkcje – oraz bezpieczne alternatywy

  • CAP_NET_BIND_SERVICE: Przypisanie do portów poniżej 1024. Alternatywa: aktywacja gniazda, umieszczenie serwera proxy odwrotnego przed serwerem.
  • CAP_NET_RAW: Gniazda surowe (Ping, DHCP). Alternatywa: mały proces pomocniczy zamiast szerokich uprawnień interpretera.
  • CAP_CHOWN/CAP_FOWNER: Właściciel/dostosowania ACL. Alternatywa: gotowe katalogi, dedykowane narzędzia do konserwacji.
  • CAP_SYS_PTRACE: Debugowanie/śledzenie – wyłącznie w środowisku stagingowym, nigdy na szeroką skalę w środowisku produkcyjnym.
  • CAP_SYS_ADMIN: „Drugie root“ – należy tego unikać; należy sprecyzować, co jest naprawdę potrzebne.

Zawsze wybieram najmniejszą ilość uprawnień, która dokładnie umożliwia uruchomienie potrzebnej funkcji. Jeśli dana funkcja otwiera kilka ścieżek ataku (np. gniazda RAW), umieszczam ją w oddzielnym, krótkotrwałym procesie, a po zakończeniu pracy cofam uprawnienia.

Lista kontrolna dla praktycznego wdrażania solidnych zdolności

  • Czy usługa uruchamia się bez uprawnień roota? Jeśli nie: dlaczego – i czy da się to rozwiązać poprzez aktywację gniazda lub za pomocą niewielkich plików binarnych wspomagających?
  • Czy wszystkie Czy przyznane uprawnienia są rzeczywiście niezbędne (potwierdzenie funkcjonalności, przypadki testowe)?
  • Czy zbiór ograniczający jest ustalony w sposób maksymalnie wąski i wcześnie?
  • Czy atrybuty XAttrs są zachowywane w sposób spójny podczas kompilacji, wdrażania i tworzenia kopii zapasowych (flagi rsync/tar, skrypty pakietów)?
  • Czy konsekwentnie rezygnuję z funkcji „Capabilities” w interpreterach i plikach binarnych z uprawnieniami SUID?
  • Czy uprawnienia właściciela i plików (Root:root, 0755/0555) oraz ścieżki są zabezpieczone przed zmianą?
  • Czy działają dodatkowe mechanizmy kontroli (NoNewPrivileges, Seccomp, profile MAC)?
  • Czy zdolności procesowe są poddawane audytowi w trakcie realizacji (/proc//status, getpcaps) oraz czy zmiany zostały udokumentowane?
  • Czy kontenery są domyślnie skonfigurowane z opcją „drop all, add minimal“ i bez opcji „privileged“?

Krótkie podsumowanie

Linux Funkcje „Capabilities” dzielą klasyczne uprawnienia roota na małe, łatwe do kontrolowania jednostki, dzięki czemu w sposób technicznie poprawny realizują zasadę minimalności. Przypisuję usługom tylko te uprawnienia, których naprawdę potrzebują, i łączę to z uprawnieniami POSIX oraz zasadami MAC. Możliwości plików (File Capabilities) zapewniają, że uprawnienia są bezpośrednio powiązane z plikami binarnymi, a audyty jasno pokazują, kto ma prawo do czego. Dzięki separacji uprawnień, ograniczonym uprawnieniom kontenerów oraz filtrom wywołań systemowych ograniczam szkody w przypadku wykorzystania luki w zabezpieczeniach. Regularne kontrole, rygorystyczne uprawnienia właścicielskie i do zapisu oraz udokumentowany proces wydawania nowych wersji pozwalają zachować oszczędność przy przyznawaniu uprawnień. Dzięki temu usługa serwerowa pozostaje sprawna, ale Pole manewru dla atakujących jest konsekwentnie ograniczana.

Artykuły bieżące

Serwerownia z serwerami z systemem Linux oraz ikoną ostrzegawczą dotyczącą luki w zabezpieczeniach jądra GhostLock
Bezpieczeństwo

GhostLock CVE – Analiza techniczna luki w zabezpieczeniach jądra systemu Linux

GhostLock CVE-2026-43499 to krytyczna luka typu „use-after-free” w jądrze systemu Linux. W niniejszej analizie dotyczącej luki GhostLock CVE przedstawiamy łańcuch exploitów umożliwiający eskalację uprawnień do poziomu root oraz przedstawiamy konkretne zalecenia dotyczące bezpieczeństwa dla administratorów.