Linux Hardening Jeden pakiet, siedem strażników. Jak Linux podejmuje decyzję o bezpieczeństwie
Linux Hardening: Jeden pakiet, siedem strażników. Jak Linux podejmuje decyzję o bezpieczeństwie
Administratorzy często mówią o bezpieczeństwie Linuksa tak, jakby składało się z pojedynczych narzędzi. Jest firewall. Jest AppArmor. Jest Docker. Jest Fail2Ban. Każde z nich dostaje własny poradnik i własną konfigurację. Problem w tym, że prawdziwy Linux nie działa w ten sposób.
Kiedy pojedynczy pakiet trafia do serwera, nie przechodzi przez jedną bramkę bezpieczeństwa. Przechodzi przez całą serię decyzji podejmowanych w różnych miejscach jądra, przestrzeni użytkownika i usług systemowych. Większość administratorów widzi jedynie efekt końcowy: połączenie zostało zaakceptowane albo odrzucone. Znacznie ciekawsze pytanie brzmi: ile mechanizmów zdążyło podjąć decyzję, zanim aplikacja w ogóle zobaczyła pierwszy bajt danych?
Prześledźmy drogę jednego pakietu.
Scena pierwsza: ktoś puka do drzwi
Wyobraźmy sobie prosty serwer.
- Ubuntu Server
- SSH
- Nginx
- Docker
- AppArmor
- UFW
- systemd
Z Internetu nadchodzi połączenie HTTPS.
Na pierwszy rzut oka wygląda to banalnie.
Internet
│
▼
Serwer
W rzeczywistości właśnie rozpoczęła się podróż przez kilka niezależnych mechanizmów bezpieczeństwa.
Strażnik pierwszy: karta sieciowa
Pakiet trafia najpierw do interfejsu sieciowego.
Dla administratora to zwykłe eth0.
Dla kernela to sterownik urządzenia.
Już tutaj mogą działać mechanizmy takie jak:
- XDP,
- eBPF,
- sprzętowe filtry NIC.
Jeżeli administrator wykorzystuje XDP, część pakietów może zostać odrzucona jeszcze zanim kernel zacznie wykonywać większość swojej pracy.
To trochę jak ochrona przed wejściem do budynku.
Nie trzeba wpuszczać każdego do recepcji.
Strażnik drugi: Netfilter
Jeżeli pakiet przejdzie dalej, trafia do Netfilter.
To właśnie tutaj działają:
- nftables,
- iptables,
- UFW.
Schemat wygląda następująco.
Tutaj zapada pierwsza świadoma decyzja administratora.
Reguła może powiedzieć:
ALLOW TCP 443
albo:
DROP TCP 22
To jednak dopiero początek.
Strażnik trzeci: socket
Pakiet został zaakceptowany.
Jeszcze nie oznacza to, że aplikacja go otrzyma.
Kernel tworzy kontekst połączenia.
Przypisuje:
- adres źródłowy,
- port,
- stan połączenia,
- odpowiedni socket.
To właśnie dlatego polecenie:
Bash
ss -tulpn
pokazuje znacznie więcej niż listę otwartych portów.
Pokazuje miejsca, do których kernel może skierować ruch.
Strażnik czwarty: capabilities
Tutaj zaczyna się coś, czego wielu administratorów praktycznie nie używa.
Linux od dawna odchodzi od prostego modelu:
root
albo
nie-root
Pojawiły się capabilities.
Przykład.
Serwer WWW potrzebuje nasłuchiwać na porcie 80.
Nie musi jednak posiadać wszystkich uprawnień administratora.
Wystarczy:
CAP_NET_BIND_SERVICE
To ogromna zmiana filozofii.
Zamiast dawać pełną władzę, dajemy tylko konkretną możliwość.
Strażnik piąty: namespaces
Tutaj zaczyna się magia współczesnego Linuksa.
Wyobraźmy sobie dwa kontenery.
Każdy uruchamia proces o numerze PID 1.
Brzmi niemożliwie.
A jednak działa.
Dlaczego?
Bo każdy żyje w innym namespace.
Host
├── PID 2314
├── PID 8821
│
├── Namespace A
│ └── PID 1
│
└── Namespace B
└── PID 1
To właśnie namespaces sprawiają, że kontenery mogą wyglądać jak osobne systemy.
Nie są pełnymi maszynami wirtualnymi.
Ale dostają własny punkt widzenia na świat.
Strażnik szósty: AppArmor lub SELinux
Dopiero teraz wchodzimy do mechanizmów MAC.
Wyobraźmy sobie, że aplikacja została przejęta.
Bez dodatkowych ograniczeń mogłaby próbować czytać kolejne katalogi.
AppArmor może powiedzieć:
Możesz czytać:
/var/www/
Nie możesz czytać:
/home/admin/
SELinux podejdzie do tego inaczej – przez konteksty bezpieczeństwa.
Cel pozostaje jednak podobny.
Proces nie powinien robić wszystkiego tylko dlatego, że udało się go uruchomić.
Strażnik siódmy: seccomp
To mój ulubiony mechanizm.
Dlaczego?
Bo działa na poziomie wywołań systemowych.
Wyobraźmy sobie proces.
Chce wykonać:
open()
Kernel pyta:
Czy ta operacja jest dozwolona?
Jeżeli profil seccomp odpowie:
DENY
proces dostaje odmowę.
To niezwykle precyzyjna kontrola.
Nie blokujemy programu.
Blokujemy konkretną czynność.
A gdzie w tym wszystkim jest systemd?
Wiele osób traktuje systemd wyłącznie jako menedżera usług.
To ogromne niedocenienie jego możliwości.
Nowoczesny systemd potrafi tworzyć własne ograniczenia.
Przykładowo:
INI
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
NoNewPrivileges=yes
Nagle usługa otrzymuje własny świat.
Nie przez kontener.
Nie przez VM.
Przez odpowiednią konfigurację jednostki.
To jeden z najbardziej niedocenianych elementów współczesnego hardeningu.
Jeden proces, wiele granic
Wyobraźmy sobie teraz prawdziwy scenariusz.
Napastnik wykorzystuje podatność RCE w aplikacji.
Proces startuje jako użytkownik www-data.
Od tej chwili kolejne mechanizmy zaczynają ograniczać jego możliwości.
Najciekawsze jest to, że żaden z tych mechanizmów samodzielnie nie daje pełnej ochrony.
Razem tworzą coś znacznie silniejszego.
Dlaczego to podejście jest skuteczniejsze niż „jedno narzędzie”
W bezpieczeństwie istnieje zasada, którą bardzo lubię.
Nie zakładaj, że jedna warstwa będzie działała zawsze.
Załóż, że każda kiedyś zawiedzie.
Jeżeli firewall przepuści legalny ruch…
AppArmor nadal działa.
Jeżeli aplikacja zostanie przejęta…
seccomp nadal działa.
Jeżeli proces potrzebuje tylko jednego capability…
nie dostaje pozostałych.
To właśnie jest Defense in Depth.
Nie teoria.
Codzienna praktyka współczesnego Linuksa.
Eksperyment, który warto wykonać
Na własnym serwerze można bardzo łatwo zobaczyć, jak te warstwy współpracują.
Najpierw sprawdź otwarte porty.
Bash
ss -tulpn
Potem capabilities procesu.
Bash
getcap -r /usr/sbin
Następnie status AppArmor.
Bash
apparmor_status
Na końcu aktywne namespace.
Bash
lsns
Nagle okazuje się, że system, który wydawał się „po prostu Linuksem”, składa się z wielu niezależnych granic bezpieczeństwa.
Największa zmiana ostatnich lat
Jeszcze dziesięć lat temu wielu administratorów koncentrowało się głównie na firewallu.
Dzisiaj granice bezpieczeństwa przesuwają się coraz bliżej samego jądra.
- eBPF obserwuje zachowanie systemu.
- seccomp filtruje syscalle.
- AppArmor ogranicza procesy.
- Capabilities rozbijają władzę roota.
- Namespaces izolują środowiska.
- systemd tworzy piaskownice dla usług.
To nie jest zbiór przypadkowych technologii.
To ewolucja jednego pomysłu.
Linux coraz rzadziej pyta:
„Czy użytkownik jest rootem?”
Coraz częściej pyta:
„Czy ten konkretny proces naprawdę potrzebuje wykonać właśnie tę operację?”
I właśnie w tej zmianie kryje się największa siła współczesnego hardeningu.







