[4] PF – Packet Filter – Uzupełnianie wstępnego zestawu reguł

31 stycznia 2024 Wyłączono przez Adam [zicherka] Nogły

Zajmijmy się teraz rozbudowaniem naszego wstępnego zestawu reguł, a także jego reorganizacją, by pracować z bardziej zaawansowanymi funkcjami.

Makra i tabele

We wstępnym zestawie reguł zakodowaliśmy na stałe wszystkie parametry w każdej regule, to znaczy numery portów tworzące listy. W przyszłości może to stać się niemożliwe do zarządzania, w zależności od charakteru sieci, a w szczególności od jej wielkości (ilości kart sieciowych). Dla celów organizacyjnych PF zawiera makra, listy i tabele. Listy zostały już uwzględnione bezpośrednio w regułach, ale możesz je też oddzielić od reguł i przypisać do zmiennej za pomocą makr.

Zajmijmy się więc naszym plikiem konfiguracyjnym [/etc/pf.conf] aby przenieść niektóre parametry do makr.

Najpierw jednak musimy dowiedzieć jak nazywa się nasza karta sieciowa, zatem do dzieła.

root@vfbsd01:~ # ifconfig
vmx0: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=4e403bb<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,JUMBO_MTU,VLAN_HWCSUM,TSO4,TSO6,VLAN_HWTSO,RXCSUM_IPV6,TXCSUM_IPV6,NOMAP>
        ether 00:0c:29:dc:eb:5e
        inet 192.168.100.106 netmask 0xffffff00 broadcast 192.168.100.255
        media: Ethernet autoselect
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
        inet 127.0.0.1 netmask 0xff000000
        groups: lo
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
pflog0: flags=141<UP,RUNNING,PROMISC> metric 0 mtu 33160
        groups: pflog
tun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> metric 0 mtu 1500
        options=80000<LINKSTATE>
        inet 10.8.0.1 --> 10.8.0.2 netmask 0xffffff00
        groups: tun
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
        Opened by PID 812

Nasza karta to [vmx0].

Rozpoczynamy edycję pliku konfiguracyjnego [pf.conf].

root@vfbsd01:~ # mcedit /etc/pf.conf
# dodaj na początku pliku
vmx0 = "vmx0"
icmp_types = "{ echoreq }"

Następnie zmodyfikujemy nasze uprzednio wprowadzone reguły.

. . .
pass in on $vmx0 proto tcp to port { 22 }
. . .
pass out inet proto icmp icmp-type $icmp_types
. . .

Nasze poprzednie reguły SSH i ICMP używają teraz makr. Nazwy zmiennych są oznaczane składnią znaku dolara [$] w PF. Przypisaliśmy nasz interfejs [vmx0] do zmiennej o tej samej nazwie jako formalność, co daje możliwość zmiany nazwy w przyszłości, jeśli zajdzie taka potrzeba. Inne popularne nazwy zmiennych dla publicznych interfejsów to [$pub_if] lub [$ext_if].

Następnie zaimplementujemy tabelę, która jest podobna do makra, ale zaprojektowana do przechowywania grup adresów IP. Stworzymy tabelę dla nierutowalnych adresów IP, które często odgrywają rolę w atakach typu „odmowa usługi” (denial of service – DOS). Możemy użyć adresów IP określonych w dokumencie RFC6890, który definiuje rejestry adresów IP specjalnego przeznaczenia. Nasz serwer nie powinien wysyłać ani odbierać pakietów do lub z tych adresów za pośrednictwem publicznego interfejsu.

Utworzymy tę tabelę, dodając następującą treść bezpośrednio pod makrem [icmp_types]:

. . .
table <rfc6890> { 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16   \
           172.16.0.0/12 192.0.0.0/24 192.0.0.0/29 192.0.2.0/24 192.88.99.0/24    \
           192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24            \
           240.0.0.0/4 255.255.255.255/32 }
. . .
# znak [\] oznacza przełamanie wiersza i przeniesienie do nowej linii

Jednak jeśli posiadamy serwer w lokalnej sieci – czyli nasz serwer „załapał się” na adresy określone w <rfc6890> to nie będziemy mieli z nim łączności. Zatem należy usunąć zakres NASZEJ sieci. Co w przypadku naszej konfiguracji powoduje potrzebę usunięcia zakresu [192.168.0.0/16], a zatem nasza babela wyglądać będzie następująco:

. . .
table <rfc6890> { 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16   \
           172.16.0.0/12 192.0.0.0/24 192.0.0.0/29 192.0.2.0/24 192.88.99.0/24    \
           198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 240.0.0.0/4               \
           255.255.255.255/32 }
. . .

Teraz dodajmy nasze reguły dla tabeli <rfc6890> pod regułą set skip on lo0:

. . .
set skip on lo0
block in quick on $vmx0 from <rfc6890>
block return out quick on egress to <rfc6890>
. . .

Tutaj wprowadziliśmy opcję zwrotu [return], która uzupełnia naszą regułę blokowania [block out]. Spowoduje to odrzucenie pakietów, a także wysłanie wiadomości RST do hosta, który próbował nawiązać te połączenia, co jest przydatne do analizy aktywności hosta. Następnie dodaliśmy słowo kluczowe [egress], które automatycznie wyszukuje domyślną trasę (trasy) w dowolnym interfejsie (interfejsach). Jest to zwykle czystsza metoda znajdowania tras domyślnych, zwłaszcza w przypadku złożonych sieci. Słowo kluczowe [quick] wykonuje reguły natychmiast, bez uwzględniania reszty zestawu reguł. Na przykład, jeśli pakiet z nielogicznym adresem IP próbuje połączyć się z serwerem, chcesz natychmiast przerwać połączenie i nie ma powodu, aby przepuszczać ten pakiet przez pozostałą część zestawu reguł.

Ochrona portu SSH

Ponieważ nasz port SSH jest otwarty dla wszystkich, może być wykorzystywany do ataku na nasz serwer. Jednym z bardziej oczywistych znaków ostrzegawczych atakującego jest masowa liczba prób logowania. Na przykład, jeśli ten sam adres IP próbuje zalogować się na nasz serwer dziesięć razy w ciągu jednej sekundy, możemy założyć z prawie 100% pewnością, że nie zostało to zrobione ludzkimi rękami, ale oprogramowaniem komputerowym, które próbuje złamać hasło logowania. Tego rodzaju systematyczne exploity są często określane jako ataki typu brute force i zwykle odnoszą sukces, jeśli serwer ma słabe hasła.

PF ma wbudowane funkcje do obsługi ataków [brute force ] i innych podobnych ataków. Dzięki PF możemy ograniczyć liczbę jednoczesnych prób połączenia dozwolonych przez pojedynczy host. Jeśli host przekroczy te limity, połączenie zostanie zerwane, a host zostanie zablokowany na serwerze. Aby to osiągnąć, użyjemy mechanizmu przeciążenia PF, który utrzymuje tabelę zabronionych adresów IP.

Zmodyfikujemy poprzednią wpisaną regułę SSH, aby ograniczyć liczbę jednoczesnych połączeń z jednego hosta zgodnie z poniższymi instrukcjami:

. . .
pass in on $vmx0 proto tcp to port { 22 } \
        keep state (max-src-conn 15, max-src-conn-rate 3/1, \
                overload <bruteforce> flush global)
. . .

Dodaliśmy opcję [keep state], która umożliwia zdefiniowanie kryteriów stanu dla tabeli przeciążenia. Przekazujemy parametr [max-src-conn], aby określić liczbę dozwolonych jednoczesnych połączeń z jednego hosta na sekundę, oraz parametr [max-src-conn-rate], aby określić liczbę dozwolonych nowych połączeń z jednego hosta na sekundę. Określiliśmy limit 15 połączeń dla [max-src-conn] i limit 3 nowych połączeń dla [max-src-conn-rate]. Jeśli te limity zostaną przekroczone przez hosta próbującego się połączyć, mechanizm przeciążenia dodaje źródłowy adres IP do tabeli <bruteforce>, co powoduje zablokowanie dostępu do serwera. Wreszcie opcja [flush global] natychmiast zrywa połączenie.

Zdefiniowaliśmy tabelę przeciążenia w regule SSH, ale nie zadeklarowaliśmy tej tabeli w zestawie reguł. Dlatego zrobimy to teraz. Zatem dodajmy tabelę <bruteforce> pod macrem [icmp_types]:

icmp_types = "{ echoreq }"
table <bruteforce> persist

Słowo kluczowe [persist] pozwala na istnienie pustej tabeli w zestawie reguł. Bez tego PF będzie narzekał (czyli po prostu wypisze ostrzeżenie), że w tabeli nie ma adresów IP.

Te środki zapewnią, że nasz port SSH jest chroniony przez potężny mechanizm bezpieczeństwa. PF umożliwia skonfigurowanie szybkich rozwiązań chroniących przed katastrofalnymi formami eksploatacji.

Oczyszczanie ruchu

Ze względu na złożoność zestawu protokołów TCP/IP i wytrwałość złośliwych ludzi, którzy szkodzą normalnym użytkownikom Internetu, pakiety często docierają z rozbieżnościami i niejasnościami, takimi jak nakładające się fragmenty IP, fałszywe adresy IP i inne. Konieczne jest oczyszczenie ruchu przed wejściem do systemu. Technicznym terminem tego procesu jest normalizacja [normalization].

Kiedy dane przesyłane są przez Internet, są zazwyczaj dzielone u źródła na mniejsze fragmenty, aby dostosować się do parametrów transmisji hosta docelowego, gdzie są ponownie składane w kompletne pakiety. Niestety intruz może przejąć ten proces na wiele sposobów, które wykraczają poza zakres tej książki. Jednak dzięki PF możesz zarządzać fragmentacją pakietow za pomocą jednej reguły. PF zawiera słowo kluczowe [scrub], które normalizuje pakiety.

Dodaj słowo kluczowe [scrub] bezpośrednio przed regułą [block all]:

. . .
set skip on lo0
scrub in all fragment reassemble max-mss 1440
block in quick on egress from <rfc6890>
block return out quick on egress to <rfc6890>
block all
. . .

Ta reguła stosuje czyszczenie do całego ruchu przychodzącego. Dołączamy opcję ponownego składania fragmentów pakietu, która zapobiega przedostawaniu się pojedynczych fragmentów pakietu do systemu. Zamiast tego są one przechowywane w pamięci podręcznej, dopóki nie zostaną ponownie złożone w kompletne pakiety, co oznacza, że reguły filtrowania będą musiały walczyć tylko z jednolitymi pakietami. Dołączamy również opcję [max-mss 1440], która reprezentuje maksymalny rozmiar segmentu [maximum segment size] ponownie składanych pakietów TCP, znany również jako ładunek [payload]. Określamy wartość 1440 bajtów, co zapewnia równowagę między rozmiarem a wydajnością, pozostawiając dużo miejsca na nagłówki.

Innym ważnym aspektem fragmentacji jest termin znany jako maksymalna jednostka transmisji (MTU maximum transmission unit). Protokoły TCP/IP umożliwiają urządzeniom negocjowanie rozmiarów pakietów w celu nawiązywania połączeń. Host docelowy używa komunikatów ICMP do informowania źródłowego adresu IP o swojej jednostce MTU, co jest procesem znanym jako wykrywanie ścieżki [path discovery] MTU. Określony typ komunikatu ICMP to miejsce docelowe nieosiągalne [destination unreachable]. Włączymy wykrywanie ścieżki MTU, dodając typ komunikatu o braku zasięgu [unreach] do listy [icmp_types].

Użyjemy domyślnej wartości MTU, która wynosi 1500 bajtów, a można ją odczytać za pomocą polecenia [ifconfig].

root@vfbsd01:~ # ifconfig
vmx0: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500 # <-tutaj
        options=4e403bb<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,JUMBO_MTU,VLAN_HWCSUM,TSO4,TSO6,VLAN_HWTSO,RXCSUM_IPV6,TXCSUM_IPV6,NOMAP>
        ether 00:0c:29:dc:eb:5e
        inet 192.168.100.106 netmask 0xffffff00 broadcast 192.168.100.255
        media: Ethernet autoselect
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

Zatem zaktualizujmy listę [icmp_types] o typ komunikatu o nieosiągalnym miejscu docelowym [destination unreachable].

vmx0 = "vmx0"
icmp_types = "{ echoreq unreach}"
. . .

Teraz, mamy już ustalone zasady obsługi fragmentacji, pakiety wchodzące do twojego systemu będą jednolite i spójne – co jest bardzo pożądane.

Teraz będziemy pracować nad zapobieganiem innym problemom związanym z bezpieczeństwem, znanym jako fałszowanie adresów IP (IP spoofing). Atakujący często zmieniają swoje źródłowe adresy IP, aby wyglądały tak, jakby znajdowały się w zaufanym węźle w sieci. PF zawiera dyrektywę [antispoofing] do obsługi sfałszowanych źródłowych adresów IP. Po zastosowaniu tej dyrektywy do określonego interfejsu (interfejsów) funkcja antispoofing blokuje cały ruch z sieci tego interfejsu (chyba że pochodzi z tego interfejsu). Na przykład, jeśli zastosujesz funkcję ochrony przed podszywaniem się do interfejsu (interfejsów) znajdującego się pod adresem 100.200.100.1/24, cały ruch z sieci 100.200.100.0/24 nie będzie mógł komunikować się z serwerem, chyba że pochodzi z tego interfejsu (interfejsów).

Dodajmy następującą treść do pliku [/etc/pf.conf], aby zastosować funkcję ochrony przed podszywaniem się do interfejsu vmx0:

. . .
set skip on lo0
scrub in all fragment reassemble max-mss 1440
antispoof quick for $vmx0
block in quick on egress from <rfc6890>
block return out quick on egress to <rfc6890>
block all
. . .

Zapisz plik i zamknij go.

Ta reguła zapobiegająca fałszowaniu adresów IP mówi, że cały ruch z sieci [vmx0] może przechodzić tylko przez interfejs [vtnet0] lub zostanie natychmiast odrzucony za pomocą słowa kluczowego [quick]. Atakujący nie będą mogli ukryć się w sieci [vmx0] i komunikować się z innymi węzłami w naszej sieci.

Aby zademonstrować naszą regułę zapobiegającą fałszowaniu adresów IP, wyświetlimy nasz zestaw reguł na ekranie w pełnej krasie. Reguły w PF są zwykle zapisywane w skróconej formie, ale mogą być również zapisywane w formie szczegółowej. Pisanie reguł w ten sposób jest generalnie niepraktyczne, ale może być przydatne do celów testowych, aby łatwiej zrozumieć co PF ma robić.

Wyświetlmy zatem zawartość pliku konfiguracyjnego PF [/etc/pf.conf] za pomocą polecenia [pfctl]:

root@vfbsd01:~ # pfctl -nvf /etc/pf.conf
. . .
block drop in quick on ! vmx0 inet from 192.168.100.0/24 to any
block drop in quick inet from 192.168.100.106 to any
. . .

Nasza reguła antyspoofingowa wykryła, że jest częścią sieci [192.168.100.0/24]. Antispoofing blokuje wszystkim tym sieciom komunikację z systemem, chyba że ich ruch przechodzi przez interfejs vmx0.

Nasza reguła zapobiegająca fałszowaniu jest ostatnim dodatkiem do podstawowego zestawu reguł.