Hardening Linux bez teatralnych gestów | Jarosław Kuler
← Wszystkie wpisy

Hardening Linux bez teatralnych gestów

Praktyczny hardening Linux: konfiguracja SSH, minimalizacja usług, auditd, sudoers i monitoring Wazuh. Bez teatru bezpieczeństwa.


Hardening systemu Linux to nie jednorazowy checklist do odfajkowania i zapomnienia, tylko iteracyjny proces dopasowany do konkretnego środowiska: tego, co tam działa, kto ma dostęp i jakie ryzyka są realnie istotne. Jeśli taki przegląd ma dotyczyć produkcyjnych hostów, osobno opisuję też hardening Linux i audyt serwerów.

Od czego zacząć

Pierwsza zasada: nie hardenuj w ciemno. Jeśli przejmujesz serwer po kimś innym, ten etap warto potraktować jako osobny, wcześniejszy krok — opisuję go w pierwszym audycie po przejęciu serwera Linux. Zanim zaczniesz zamykać porty i restrykcjonować uprawnienia, zbierz dane:

  • ss -tulnp: co faktycznie nasłuchuje i na jakich portach,
  • systemctl list-units --type=service --state=active: jakie usługi działają,
  • last, lastb, journalctl -u ssh: kto i skąd się logował.

Bez tego bazowego obrazu możesz wyłączyć coś krytycznego albo zignorować faktyczne wektory ataku.

SSH: minimalna powierzchnia ataku

SSH to najczęstszy punkt wejścia. Trzy zmiany, które robią największą różnicę:

# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
AllowUsers twoj-uzytkownik

Klucze SSH zamiast haseł to podstawa. Jeśli środowisko tego wymaga, rozważ dodatkowo fail2ban albo sshguard do blokowania brute-force. Port-knocking jest efektowny, ale to przykład bezpieczeństwa przez ukrywanie (security through obscurity), które nie zastępuje solidnej konfiguracji.

Minimalizacja usług

Każda działająca usługa to potencjalny wektor ataku. Zanim ją wyłączysz, ustal właściciela procesu, jego zależności i to, czy faktycznie nie jest używana. Wyłączaj po jednej usłudze naraz, sprawdzając za każdym razem, czy nic się nie posypało, zanim przejdziesz do kolejnej.

systemctl disable --now bluetooth
systemctl disable --now avahi-daemon
systemctl disable --now cups

Na serwerach headless większość tych usług jest zbędna. Sprawdź też cron, at i timery systemd: nieaktualne zadania to częsty, zapomniany punkt wejścia.

Firewall

Zamknięty port w firewallu to pierwsza linia obrony, zanim jeszcze dojdzie do warstwy aplikacji. Na nowszych dystrybucjach standardem jest nftables, choć iptables wciąż działa i wiele wdrożeń go używa.

nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input iif lo accept
nft add rule inet filter input tcp dport 22 accept

To minimalny, ale kompletny szkielet: tworzy tabelę i łańcuch, jeśli jeszcze nie istnieją, przepuszcza ruch loopback i połączenia już nawiązane (bez tego host przestanie odpowiadać na własne zapytania DNS czy aktualizacje), a dopiero potem otwiera konkretny port. Samo dodanie reguły drop na końcu istniejącego łańcucha, bez tych wyjątków, potrafi zerwać połączenia inicjowane przez sam host. Traktuj to jako szkielet do rozbudowy, nie gotową konfigurację produkcyjną: pełny ruleset lepiej oprzeć na mechanizmie właściwym dla danej dystrybucji.

Na Ubuntu i pochodnych ufw jest wygodną nakładką na nftables, na RHEL i pochodnych rolę tę pełni firewalld. W środowiskach cloudowych warto pamiętać, że firewall na hoście to dodatkowa warstwa, nie zamiennik security group czy network ACL skonfigurowanych na poziomie dostawcy.

Mandatory Access Control: SELinux i AppArmor

Standardowe uprawnienia Linuksa (DAC) mówią, kto ma dostęp do pliku. SELinux i AppArmor dodają drugą warstwę: nawet jeśli proces działa jako root albo zostanie przejęty, może wykonywać tylko te działania, na które pozwala jego profil.

SELinux jest domyślny na RHEL, Rocky i AlmaLinux:

getenforce
sestatus
ausearch -m avc -ts recent

Przełączenie w tryb permissive albo całkowite wyłączenie SELinuksa bywa najszybszym sposobem na “naprawienie” problemu z uprawnieniami, ale to rozwiązanie pozorne. Właściwa kolejność to najpierw analiza konkretnego wpisu AVC przez ausearch, sprawdzenie kontekstów plików i wartości boolean, a dopiero gdy to nie wystarcza, rozważenie własnej reguły. audit2allow generuje politykę automatycznie na podstawie odrzuconej akcji, ale równie chętnie zalegalizuje błąd wynikający ze złego kontekstu pliku czy nieprawidłowej konfiguracji aplikacji, jak faktycznie brakujące uprawnienie, więc każdą wygenerowaną nim regułę warto przejrzeć, zanim trafi do polityki.

AppArmor pełni podobną rolę na Ubuntu i Debianie, z profilami przypisanymi do konkretnych aplikacji:

aa-status
aa-enforce /etc/apparmor.d/usr.sbin.nginx

Warto wymuszać konkretny, sprawdzony profil, a nie wszystkie pliki z katalogu /etc/apparmor.d/ naraz: katalog zawiera też definicje abstractions i pliki pomocnicze, które nie są samodzielnymi profilami.

Żaden z tych mechanizmów nie jest trudny do utrzymania, jeśli wdroży się go od początku. Wyłączenie MAC na starcie projektu, bo “przeszkadza”, zwykle kończy się tym, że nikt nigdy go nie włącza z powrotem.

Parametry jądra

Domyślna konfiguracja jądra jest kompromisem między bezpieczeństwem a kompatybilnością wsteczną. Kilka parametrów sysctl warto dostosować ręcznie:

# /etc/sysctl.d/99-hardening.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
kernel.yama.ptrace_scope = 1
kernel.kptr_restrict = 2

Na serwerze, który nie pełni roli routera, IP forwarding (net.ipv4.ip_forward) powinien być wyłączony, chyba że konkretna usługa tego wymaga, na przykład kontener czy VPN. Zmiany aplikuje się przez sysctl --system i warto je trzymać w osobnym pliku w /etc/sysctl.d/, żeby przetrwały aktualizacje i były łatwe do przejrzenia.

Hasła i PAM

Klucze SSH eliminują problem haseł do samego serwera, ale konta lokalne, sudo i ewentualne logowanie konsolowe nadal tego wymagają. PAM (Pluggable Authentication Modules) pozwala wymusić sensowną politykę bez pisania własnych mechanizmów.

pam_faillock blokuje konto po serii nieudanych prób logowania:

# /etc/security/faillock.conf
deny = 5
unlock_time = 900

Przy polityce haseł warto opierać się na aktualnych rekomendacjach, nie na starych nawykach. NIST SP 800-63B-4 zaleca przede wszystkim długość hasła i sprawdzanie go względem listy znanych, skompromitowanych haseł, a odradza obowiązkowe reguły kompozycji w stylu “musi zawierać cyfrę, wielką literę i znak specjalny” oraz wymuszanie okresowej zmiany hasła bez konkretnej przesłanki, że doszło do kompromitacji. pam_pwquality może to wesprzeć, skupiając się na minimalnej długości zamiast na sztucznych regułach znakowych:

# /etc/security/pwquality.conf
minlen = 15

Sprawdzanie względem list znanych, przeciekłych haseł (na przykład przez integrację z bazą Have I Been Pwned albo lokalną listą) daje realnie więcej niż wymuszanie cyfry i znaku specjalnego, które użytkownicy i tak obchodzą, dopisując ”!” na końcu tego samego hasła co zawsze.

To nie jest miejsce na rygor dla samego rygoru. Zbyt agresywna polityka haseł prowadzi do zapisywania ich na karteczkach albo w plikach tekstowych, co jest gorsze niż rozsądny kompromis.

Hardening bez monitoringu to jak zamek bez alarmu, nie wiesz, kiedy ktoś spróbował wejść. Minimum:

  • auditd z sensownymi regułami (zmiany w /etc/passwd, /etc/sudoers, logowania root),
  • journald z persistentnym logiem (/var/log/journal/),
  • centralny odbiorca logów: jeśli serwer padnie lub zostanie skompromitowany, logi powinny być gdzie indziej.

Wazuh jako SIEM nadaje się dobrze do środowisk, gdzie chcesz korelować zdarzenia z wielu maszyn. Uruchomienie agenta na każdym serwerze daje widoczność, której nie osiągniesz przez ręczne przeglądanie logów.

Uprawnienia i sudoers

Zasada minimalnych uprawnień: użytkownik ma dostęp tylko do tego, czego realnie potrzebuje.

# Sprawdź kto ma sudo
getent group sudo
grep -r 'ALL=(ALL)' /etc/sudoers /etc/sudoers.d/

# Ogranicz konkretne komendy
%devops ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx

Unikaj NOPASSWD: ALL. Nie “niweluje sensu oddzielnych kont” (identyfikacja użytkownika, audyt i możliwość odebrania dostępu pojedynczej osobie nadal działają), ale usuwa dodatkową barierę ponownego uwierzytelnienia przed eskalacją do roota i zwiększa skutki przejęcia sesji tego użytkownika.

Warto też pamiętać, że ograniczenie sudo do jednej komendy, jak w przykładzie powyżej, nie gwarantuje bezpieczeństwa samo z siebie. Jeśli użytkownik devops może modyfikować unit nginx, jego EnvironmentFile albo skrypty wywoływane przez tę usługę, to w praktyce może uzyskać wykonanie dowolnego kodu jako root przy następnym restarcie, mimo że sudoers pozwala mu jedynie na systemctl restart nginx.

Warto też przejrzeć system plików pod kątem binarek SUID i SGID, bo to klasyczny wektor eskalacji uprawnień, jeśli któraś z nich ma podatność:

find / -xdev -perm -4000 -type f 2>/dev/null
find / -xdev -perm -2000 -type f 2>/dev/null

Katalogi takie jak /tmp i /var/tmp, do których zapisują różni użytkownicy i procesy, dobrze jest montować z opcjami noexec, nosuid i nodev w /etc/fstab, o ile żadna aplikacja nie wymaga tam uruchamiania plików wykonywalnych. To warstwa utrudniająca, nie twarda granica bezpieczeństwa: nie blokuje wykonania kodu przez interpreter (na przykład python3 /tmp/skrypt.py), a bywa, że łamie aplikacje, które faktycznie potrzebują wykonywać coś z tych katalogów.

Integralność plików

Zmiana konfiguracji, podmiana binarki albo dopisanie backdoora do skryptu startowego to działania, które dobry atakujący stara się ukryć. Monitoring logów tego nie wykryje, jeśli sam log zostanie zmodyfikowany albo zdarzenie nigdy się w nim nie pojawi.

AIDE (Advanced Intrusion Detection Environment) buduje bazę sum kontrolnych krytycznych plików i systematycznie sprawdza, czy coś się zmieniło:

aide --init
mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
aide --check

Bazę referencyjną warto zbudować od razu po instalacji i hardeningu systemu, zanim cokolwiek zdąży się zmienić, a same skany zaplanować cyklicznie, na przykład przez cron. Powiadomienie o zmianie w /etc/passwd czy /bin/bash, których nikt świadomie nie edytował, to sygnał wymagający natychmiastowej reakcji.

Baza referencyjna przechowywana wyłącznie na tym samym, monitorowanym hoście ma jedną słabość: skuteczny atakujący, który już ma dostęp do systemu, może podmienić ją razem z plikami, które miała pilnować. Dla mocniejszej gwarancji baza albo przynajmniej jej podpis kryptograficzny powinny trafiać w miejsce, którego host nie może samodzielnie modyfikować, na przykład na zewnętrzny serwer logów albo nośnik tylko do odczytu.

Wykrywanie rootkitów

AIDE wykryje zmianę pliku, ale nie zawsze powie, że ta zmiana jest rootkitem. rkhunter i chkrootkit specjalizują się właśnie w tym: sprawdzają znane sygnatury, podejrzane moduły jądra, ukryte procesy i typowe miejsca, w których złośliwe oprogramowanie się zagnieżdża.

rkhunter --update
rkhunter --check
chkrootkit

Żadne z tych narzędzi nie daje stuprocentowej pewności, dobry rootkit potrafi ukryć się przed lokalnym skanerem, ale jako tania, cykliczna kontrola w tle są warte swojej ceny. Warto uruchamiać je regularnie przez cron i traktować wynik jako jeden z sygnałów, nie ostateczny wyrok.

Audyt i pomiar

Trudno ocenić, jak dobrze utwardzony jest system, bez punktu odniesienia. Lynis to darmowe narzędzie, które skanuje konfigurację i wskazuje konkretne braki:

lynis audit system

Wynik zawiera listę ostrzeżeń, sugestii i tzw. hardening index, liczbę porządkującą postęp w czasie. Tak zebrane wskaźniki mają sens dopiero w połączeniu z bieżącym monitoringiem — przykład takiego połączenia pokazuję przy projekcie monitoring i hardening systemów Linux. CIS Benchmark dla danej dystrybucji idzie o krok dalej: to rozbudowany dokument z konkretnymi, numerowanymi rekomendacjami, które można wdrażać punkt po punkcie i traktować jako listę odniesienia przy audytach czy zgodności z regulacjami.

Żadne z tych narzędzi nie zastępuje zrozumienia, co się konfiguruje. Ślepe wdrażanie każdej rekomendacji CIS bez sprawdzenia, czy nie łamie działającej aplikacji, jest tym samym błędem co brak hardeningu w ogóle, tylko w drugą stronę.

Czego nie robić

  • Nie zmieniaj domyślnego portu SSH jako głównej ochrony, to security theater, nie hardening.
  • Nie blokuj wszystkiego naraz na produkcji, testuj na stagingu.
  • Nie ignoruj aktualizacji bezpieczeństwa: unattended-upgrades dla Debiana i Ubuntu to dobry punkt startowy.

Hardening to kompromis między bezpieczeństwem a użytecznością. Cel to świadome decyzje, nie maksymalna restrykcyjność.