Showing posts with label iptables. Show all posts
Showing posts with label iptables. Show all posts

Thursday, November 4, 2010

"icmp-host-prohibited"?


Znalazłem taką opcje w zaporze RH Fedory, niby domyślna polityka ustawiona jest na ACCEPT a jednak ładnie wszystko odrzuca. Postanowiłem zbadać tą sprawę. Gdzieś w sieci natrafiłem na link do RFC1812, gdzie można trochę o tym poczytać:
5.3.9 Packet Filtering and Access Lists
...
   The router SHOULD allow an appropriate ICMP unreachable message to be
   sent when a packet is discarded.  The ICMP message SHOULD specify
   Communication Administratively Prohibited (code 13) as the reason for
   the destination being unreachable.

Przeczytałem gdzieś również, że ktoś po testach stwierdził, iż rzeczywiście używanie tej opcji dla odrzucanych pakietów oszczędza pasmo. Postanowiłem również to przetestować.

Test jest prosty, na serwerze dodałem na samym końcu regułę:
iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited

Następnie użyłem prostego badania zamkniętego portu, pierwszy wynik jest baz powyższej reguły, a drugi już z:

root@home:/home/grzesiek# nmap x.xxx.pl -p 33 -PN

Starting Nmap 5.00 ( http://nmap.org ) at 2010-11-04 21:17 CET
Interesting ports on x.xxx.pl (XX.18.194.XXX):
PORT   STATE    SERVICE
33/tcp filtered dsp

Nmap done: 1 IP address (1 host up) scanned in 2.20 seconds

Trwało to 2 sekundy, ale długo :)


# tcpdump -i eth0:1 port 33
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0:1, link-type EN10MB (Ethernet), capture size 96 bytes
21:16:54.948070 IP PC-77-XX-24-XXX.xxx-net.pl.45056 > x.xxx.pl.33: S 2889941185:2889941185(0) win 2048 <mss 1460>
21:16:55.949049 IP PC-77-XX-24-XXX.xxx-net.pl.45057 > x.xxx.pl.33: S 2889875648:2889875648(0) win 2048 <mss 1460>
^C
2 packets captured
2 packets received by filter
0 packets dropped by kernel


Zostały odebrane dwa pakiety.
Teraz wersja z wysyłaniem przez serwer komunikatu zwrotnego ICMP:

root@home:/home/grzesiek# nmap x.xxx.pl -p 33 -PN

Starting Nmap 5.00 ( http://nmap.org ) at 2010-11-04 21:52 CET
Interesting ports on x.xxx.pl (83.18.194.164):
PORT   STATE    SERVICE
33/tcp filtered dsp

Nmap done: 1 IP address (1 host up) scanned in 0.17 seconds

Od razu widać - szybciej.


# tcpdump -i eth0:1 port 33
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0:1, link-type EN10MB (Ethernet), capture size 96 bytes
21:51:59.989134 IP PC-XX-46-24-XXX.xxxx-net.pl.55250 > x.xxx.pl.33: S 868347170:868347170(0) win 1024 <mss 1460>
^C
1 packets captured
1 packets received by filter
0 packets dropped by kernel

Drugi pakiet był już zbędny, ponieważ nmap dostał jasny komunikat dlaczego port nie odpowiada - administracyjnie zabroniony.


Sunday, July 11, 2010

Zapora ogniowa w Linuksie na komputerze domowym

Wiele osób pyta jak skonfigurować zaporę na komputerze domowym, często jest to podobna konfiguracja więc dlaczego nie stworzyć pewnego wzorca?

Przygotowałem taki wzorzec, nie sprawdzałem go pod każdym kątem więc może coś nie działać, zwłaszcza na desktopie, dlaczego? Ponieważ to właśnie na nim więcej jest oprogramowania, lub jak kto woli bajerów np. komunikatory itp. itd.. Tak więc postawiłem za cel sobie napisanie takiej zapory, aby tworzyła najlepszy kompromis między bezproblemowym użytkowaniem komputera (wygoda), bezpieczeństwem i prędkością.

Najpierw należy wspomnieć o założeniach zapory (polityce):
  • zapora korzysta z architektury klient-serwer, desktop jest klientem, jego użytkownik może inicjować połączenia (porty źródłowe aplikacji klienckich to zakres 1024-65535), serwery nie mogą inicjować połączeń z naszym komputerem - na tej samej zasadzie działa przekazywanie ruchu z mechanizmem NAT
  • odpowiedz serwera jest akceptowana poprzez mechanizm filtrowania stateful
  • zapora zezwala na przyjmowanie, wysyłanie i przekazywanie komunikatów protokołu ICMP (np. polecenie ping)
  • zapora zezwala na przyjmowanie pakietów kierowanych do interfejsu local loopback, czyli lo
  • zapora udostępnia dwie usługi, pierwsza to zasoby sieciowe jakie oferuje pakiet Samba (często użytkownicy instalują taki serwer na potrzeby wymiany plików wewnątrz sieci prywatnej - domu) oraz SSH, czyli szyfrowany terminal, jest on dostępny również z zewnątrz a nie tylko z wewnątrz jak SMB
Ponieważ zapora wykorzystuje mechanizm NAT należy uruchomić tą funkcję usuwając znak komentarza '#' przed linią '# net.ipv4.ip_forward=1' w pliku /etc/sysctl.conf

root@home:/home/grzesiek# cat /etc/sysctl.conf | grep forward
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
# Uncomment the next line to enable packet forwarding for IPv6
#net.ipv6.conf.all.forwarding=1

Następnie należy przeładować konfigurację poleceniem:
root@home:/home/grzesiek# sysctl -p /etc/sysctl.conf
net.ipv4.ip_forward = 1


Została jeszcze tylko kwestia nazwy interfejsu, którym ISP dostarcza nam połączenie (Internet), w moim przypadku jest to eth2, Twój może nazywać się zupełnie inaczej, zazwyczaj eth0 (możesz to sprawdzić wydając polecenie ifconfig). Zakładam, iż sieć wewnętrzna należy do klasy 192.168.0.0/24, czyli np. 192.168.4.0, 192.168.42.0 itp..

Oto kod konfiguracji zapory:

#!/bin/sh
##
##   DESKTOP FIREWALL by grzesiek ;)
##
##

start () {

iptables -F
iptables -X
iptables -F -t nat
iptables -X -t nat

iptables -P FORWARD DROP
iptables -P INPUT DROP
iptables -P OUTPUT DROP

# INPUT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i eth1 -s 192.168.0.0/16 -p udp -m multiport --dport 137,138 -m state --state NEW -j ACCEPT
iptables -A INPUT -i eth1 -s 192.168.0.0/16 -p tcp -m multiport --dport 139,445 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
#
# tu możemy zezwalać na łączenie się z innymi usługami świadczonymi przez nasz komputer
# np.
# iptables -A INPUT -p tcp --dport 80 -j  ACCEPT
#
iptables -A INPUT -i lo -m state --state NEW -j ACCEPT
iptables -A INPUT -p icmp -m state --state NEW -j ACCEPT
iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited

# OUTPUT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p tcp --sport 1024:65535 -m state --state NEW -j ACCEPT
iptables -A OUTPUT -p udp --sport 1024:65535 -m state --state NEW -j ACCEPT
iptables -A OUTPUT -p icmp -m state --state NEW -j ACCEPT
iptables -A OUTPUT -j REJECT --reject-with icmp-host-prohibited

# FORWARD
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -s 192.168.0.0/255.255.0.0 -p tcp --sport 1024:65535 -m state --state NEW -j ACCEPT
iptables -A FORWARD -s 192.168.0.0/255.255.0.0 -p udp --sport 1024:65535 -m state --state NEW -j ACCEPT
iptables -A FORWARD -p icmp -m state --state NEW -j ACCEPT
iptables -A FORWARD -j REJECT --reject-with icmp-host-prohibited

# NAT system
iptables -t nat  -A POSTROUTING -s 192.168.0.0/255.255.0.0 -d 0/0 -j  MASQUERADE

echo "Configuration iptables on... (more information use option: show)"
exit 0
}

stop () {
iptables -F
iptables -X
iptables -F -t nat
iptables -X -t nat

iptables -P FORWARD ACCEPT
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT

iptables -t nat -A POSTROUTING -s 192.168.0.0/255.255.0.0 -d 0/0 -j  MASQUERADE

echo "Configuration iptables off (except NAT system for 192.168.0.0/16 !)... (more information use option: show)"
exit 0
}

show () {
echo "\n                            [ Table: filter ]\n"
iptables -L -nv --line-numbers -t filter
echo "\n                            [ Table: nat ]\n"
iptables -L -nv --line-numbers -t nat
echo "\n"
exit 0
}

#main:
case "$1" in 
 start)
  start ;;
 stop)
  stop ;;
 show)
  show ;;
 *)
  echo $"  Składnia: $0 {stary, stop, show}"
  echo "@@@ Firewall by grzesiek ;) @@@"
  exit 1
esac

Przedstawiony skrypt należy jeszcze dodać do rozruchu systemu, czyli do katalogu /etc/init.d/firewall. Następnie należy wykonać polecenie, które przypisze go do sekwencji startowych:

update-rc.d firewall defaults

Nasz skrypt obsługuje dwie wymagane opcje; start i stop oraz dodatkową show. Aby zobaczyć jak wygląda konfiguracja zapory należy wydać polecenie:

root@home:/home/grzesiek# /etc/init.d/firewall show

[ Table: filter ]

Chain INPUT (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1    22785 6326K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED 
2        0     0 ACCEPT     udp  --  eth1   *       192.168.0.0/16       0.0.0.0/0           multiport dports 137,138 state NEW 
3        2   120 ACCEPT     tcp  --  eth1   *       192.168.0.0/16       0.0.0.0/0           multiport dports 139,445 state NEW 
4       10   584 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:22 state NEW 
5       18  1080 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           state NEW 
6        2   144 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW 
7    12728 1068K REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain FORWARD (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1    36828   34M ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED 
2     3443  207K ACCEPT     tcp  --  *      *       192.168.0.0/16       0.0.0.0/0           tcp spts:1024:65535 state NEW 
3      174 11240 ACCEPT     udp  --  *      *       192.168.0.0/16       0.0.0.0/0           udp spts:1024:65535 state NEW 
4        7   588 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW 
5       39  2124 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1    22312 3900K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED 
2      794 47640 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           tcp spts:1024:65535 state NEW 
3     1021 68127 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0           udp spts:1024:65535 state NEW 
4       13  1188 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW 
5    14518 1240K REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

[ Table: nat ]

Chain PREROUTING (policy ACCEPT 16181 packets, 1264K bytes)
num   pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 1754 packets, 109K bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1     2941  177K MASQUERADE  all  --  *      *       192.168.0.0/16       0.0.0.0/0           

Chain OUTPUT (policy ACCEPT 15834 packets, 1320K bytes)
num   pkts bytes target     prot opt in     out     source               destination         


root@home:/home/grzesiek#

Krótki komentarz do każdej z linii:
INPUT
1 Linia: przepuszcza pakiety powrotne z połączeń, które wcześniej zainicjowaliśmy
2 Linia: przepuszcza ruch dla usługi Samba z sieci lokalnej, czyli nie przychodzącego z internetu - interfejs eth2 w moim przypadku
3 Linia: jw. porty TCP
4 Linia: zezwala na łączenie się przez SSH z każdego miejsca
5 Linia: zezwala na połączenia z interfejsem zwrotnym lo
6 Linia: zezwala na odbieranie komunikatów ICMP, czyli np. używanie polecenia ping
7 Linia: w tym miejscu wszystkie przewidziane, a tym samym dozwolone możliwości dla ruchu przychodzącego się wyczerpały, można by nie robić nic i pakiety zostałyby odrzucone ale optymalniej będzie odsyłać określony w tej regule komunikaty do nadawcy

FORWARD
1 Linia: przekazuje pakiety z połączeń, które wcześniej zainicjowali użytkownicy naszej sieci wewnętrznej - LAN
2 Linia: zezwala na przekazywanie pakietów protokołu TCP z naszej sieci do serwerów w internecie jeżeli pochodzą z portów aplikacji klienckich (1024-65535)
3 Linia: jw. porty UDP
4 Linia: zezwala na przekazywanie komunikatów ICMP między siecią WAN a LAN
5 Linia: pozostałe pakiety są odrzucane, a dla nadawcy wysyłany jest komunikat ICMP informujący, iż połączenie zostało zabronione

OUTPUT
1 Linia: zezwala na wysyłanie odpowiedzi na połączenia zezwolone przez łańcuchu INPUT
2 Linia: zezwala na wysyłanie z naszego komputera pakietów protokołu TCP do serwerów w internecie jeżeli pochodzą z portów aplikacji klienckich (1024-65535)
3 Linia: jw. porty UDP
4 Linia: zezwala na wysyłanie komunikatów ICMP z naszego komputera
5 Linia: pozostałe pakiety są odrzucane, a dla nadawcy wysyłany jest komunikat ICMP informujący, iż połączenie zostało zabronione - czyli nam :)

Czas na małe sprawdzenie, najpierw sprawdzimy jak wygląda nasza zapora z zewnątrz:

grzesiek@bvm1XX:~$ nmap 7X.X6.2X.XX5 -PN

Starting Nmap 4.62 ( http://nmap.org ) at 2010-11-07 15:18 CET
Interesting ports on PC-7X-X6-2X-XX5.xxxx-net.pl (7X.X6.2X.XX5):
Not shown: 1714 filtered ports
PORT STATE SERVICE
22/tcp open ssh

Nmap done: 1 IP address (1 host up) scanned in 8.701 seconds

oraz z sieci wewnętrznej:

[root@localhost grzesiek]# nmap 192.168.42.1 -PN

Starting Nmap 5.21 ( http://nmap.org ) at 2010-11-07 17:29 CET
Nmap scan report for 192.168.42.1
Host is up (0.00025s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE
22/tcp open ssh
139/tcp open netbios-ssn
445/tcp open microsoft-ds

MAC Address: 0A:00:27:00:00:00 (Unknown)

Nmap done: 1 IP address (1 host up) scanned in 5.43 seconds
[root@localhost grzesiek]#


Jak widać wszystko jest zablokowane, oprócz tego na co zezwoliliśmy, podczas gdy możemy bez problemów korzystać z komputera. Gdyby jednak znalazły się jakieś błędy albo należało dodać jakąś funkcjonalność proszę o komentarze. Jak pisałem wcześniej zapora była tworzona szybko więc może czegoś nie przewidziałem.

Miało być krótko a wyszło jak zwykle :)

Saturday, May 8, 2010

Iptables - bezpieczna konfiguracja reguł dla DNS

Usługa DNS wymaga osobliwego spojrzenia jeżeli chodzi o jej bezpieczeństwo. Jest też specyficzna i wyjątkowa w swoim rodzaju. Niemal jak w łańcuchu pokarmowym zwierząt serwer DNS w usługach sieciowych stanowi pierwsze ogniwo dla większości usług.

Z punktu widzenia bezpieczeństwa protokół DNS oraz serwery tej usługi są dość wymagające. Po pierwsze używają protokołu transportowego UDP, który nie wymaga nawiązania sesji między klientem a serwerem. Po drugie są bardzo podatne na ataki ponieważ protokół DNS był projektowany w czasach gdy jeszcze nie było takiego zagrożenia jak teraz dla sieci. Obecnie trwają prace nad aktualizacja protokołu DNS na DNSSEC obsługujący szyfrowaną autoryzację [1]. Nawet systemy aktywnego przeciwdziałania, czyli systemy IPS nie działające w trybie inline są bezradne. Przedstawiony system IPS zaimplementowany i opisany wcześniej na łamach mojego bloga nie ma żadnych szans w obronie przed atakiem typu DoS na lokalny serwer DNS.
Aby zrozumieć specyfikę ataku i możliwości obrony podam przykład takiego ataku: http://bugs.debian.org/cgi-bin/bugre...g=5;bug=538975

Przykład ten wykorzystuje błąd serwera. Obrona przed nim jest trudna, ponieważ cały atak wymaga przesłania tylko jednego pakietu UDP, czyli gdyby system IPS outline nawet wykrył ten atak było by już za późno na jakąkolwiek interakcję.
Iptables mógłby wcielić się w rolę systemu IPS inline i przy użyciu np. modułu string zablokować opisane pakiety. Niemniej jednak atak musi być dobrze znany, a opisanie nie będzie zbyt problematyczne. Wydaje im się, że rozwiązaniem tego problemu będzie nieco inna strategia niż 'ściganie przestępcy'. Lepszym rozwiązaniem będzie zbudowanie takich reguł, które ograniczą w logiczny sposób możliwe największa ilość ataków przy jednoczesnym zachowaniu maksymalnej wydajności zapory.
Złą informacją jest dla nas to, że atak na serwer DNS ma postać typu "one shot", dobrą to, że zawsze znamy adresy IP serwerów DNS wyższego poziomu, z których powinniśmy korzystać. Należy to wykorzystać.
Jeżeli Twoja reguła dotycząca usługi DNS wygląda mniej więcej tak:

iptables -A INPUT -p udp --dport 53 -j ACCEPT

to znaczy, że powinieneś uważnie przeczytać ten artykuł.

Wariantów jest dużo, ja opisuję tu przykład gdy na serwerze - bramie sieci prywatnej - znajduje się serwer DNS buforujący.

Logicznie najlepszym rozwiązaniem byłoby pozwalać na łączenie się tylko ze znanymi nam adresami IP serwerów DNS. Nawet bez uwzględniania portu 53/UDP w taki sposób zabezpieczamy nasz serwer przed skryptami ogólnie dostępnymi w sieci do wykonania ataku DoS. Napastnik musiałby sfałszować adres IP naszego serwera DNS wyższego poziomu aby pakiet się przedostał przez zaporę co nie jest trudnym zadaniem. Jednak takie rozwiązanie na sam początek jest na pewno drogą w dobrym kierunku, ogranicza w znacznym stopniu napastników tylko do tych, którzy znają adresy IP naszych DNS-ów. Dobrą wiadomością jest też to, że nie z wszystkich sieci sfałszowany pakiet się wydostanie, pod warunkiem, że ISP poprawnie skonfigurował routery. Nasz router również powinien być skonfigurowany w taki sposób aby nie przepuszczał pakietów z poza innej sieci niż naszych wewnętrznych.
Gdy ktoś się podszywa pod honorowane przez nas adresy prawie na 90% zostawi po tym ślad w pakiecie protokołu DNS. Możesz wyłapać dużą próbkę pakietów od swojego serwera DNS i wyszukać w nich wspólną cechę, może to być wartość TTL - ale nie zawsze musi być taka sam. Modułu u32 pakietu iptables, którego zadaniem jest testowanie wybranych bajtów może okazać się tu bardzo przydatny.

Kolejną kwestią wykorzystania DNS jest chociażby atak na wspomniany przeze mnie IPS w Pythonie. Prezentowałem w jaki sposób zablokować adres IP, z którego miało miejsce zbyt nadmierne w krótkim czasie logowanie do usługi SSH. Sprytny napastnik mógłby wykorzystać to na swoją korzyść. Wystarczyłoby aby podawał się za serwer DNS z którego korzystamy aby zablokować całą wewnętrzną sieć. Nasz IPS, który miał nasz chronić teraz zablokował by nam usługę DNS! Na szczęście sesję TCP, bo z takiej korzysta SSH nie tak łatwo podrobić, ale gdy byśmy blokowali inne połączenia korzystające z protokołu UDP byłoby to proste i możliwe. Na szczęście serwerów DNS zawsze mamy więcej niż jeden. Dlatego przed regułami wstawianymi przez skrypt IPS należy umieścić regułę, która zadba o to aby kontakt z naszymi serwerami wyższego poziomu nie został zerwany.

W końcu z pomocą mechanizmu pamięci stanów ograniczymy możliwości komunikacji dla protokołu tylko do niezbędnego minimum. Nasz serwer DNS buforujący najczęściej wysyła pakiet z zapytaniem do serwera nadrzędnego, czyli łańcuch OUTPUT. Możemy również ograniczyć te zapytania do tylko znanych nam adresów. Serwer wyższego poziomu odpowiada na zapytanie naszego serwera. Jest to odpowiedź na nasze zapytanie więc jądro takiemu pakietowi przypisze stan ESTABLISHED i oddzielna reguła przepuszczająca pakiety z portu 53/UDP w łańcuchu INPUT staje jest zbędna. Datagramy UDP z protokołem DNS zawierającym odpowiedź przepuści reguła zezwalająca na połączenia już nawiązane.

Serwer DNS odbiera i odpowiada z portu 53/UDP, czasami również i 53/tcp ale bardzo rzadko - między serwerami gdy pakiety są bardzo duże podczas transferów stref oraz gdy odpowiedź serwera jest zbyt duża i wymaga buforowania. W standardowych przypadkach jednak nie ma to miejsca, chyba, że masz zapasowy (ang. secondary) serwer DNS.
Klient korzystający z usługi DNS używa portów dynamicznych UDP powyżej 1023.

Najlepszymi regułami powinny być więc takie:

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -d [IP_SERWER_DNS_1] -p udp --dport 53 -m state --state NEW -j ACCEPT
iptables -A OUTPUT -d [IP_SERWER_DNS_2] -p udp --dport 53 -m state --state NEW -j ACCEPT


Poza zwiększeniem bezpieczeństwa reguły te zwiększają wydajność zapory, ponieważ redukują ilość reguł zwłaszcza dla łańcucha INPUT. Łańcuch OUTPUT również możemy poddać optymalizacji usuwając warunek przeznaczenia (IP) dla pakietów wychodzących na port 53/UDP.
Jeżeli to konieczne powinieneś dodać jeszcze reguły dla portu 53/TCP w łańcuchu OUTPUT.

Tuesday, March 30, 2010

Optymalizacja zapory dzięki mechanizmowi filtrowania z pamięcią stanu

Jedną z najważniejszych technologii wdrożonych do zapory ogniowej iptables przez netfilter.org jest filtrowanie z pamięcią stanu SPI (ang. Stateful Packet Inspection). Jest to mechanizm charakteryzujący rozwiązania programowe z najwyższej półki w tej dziedzinie informatyki. Dlatego warto przyjrzeć mu się bliżej.
Sam cel wprowadzenia takiej techniki wiązał się z optymalizacją filtrowania. Dzięki śledzeniu stanów połączeń zapora może podejmować decyzje na podstawie tylko pierwszego pakietu, kolejne odziedziczą ją i nie będą musiały być również sprawdzane (mechanizm ten nie ogranicza się tylko do sesji TCP).
Aby uzmysłowić sobie jaki to krok na przód w wydajności zapory należy przeanalizować statystyki zapory działającej dłuższy czas w średniej wielkości sieci. Służy do tego opcje:

iptables -L -nv --line-numbers

Z moich wyliczeń (statystyki pochodzą z wdrożonej na serwerze produkcyjnym zapory dla sieci ok. 100 komputerów) wynika, że 97,87% pakietów jest dopasowywanych do pierwszej reguły, która przepuszcza pakiety należące do sesji/połączeń już nawiązanych. Czyli tylko 2.13% pakietów jest dopasowywanych do kolejnych reguł. Daje to duże przyzwolenie na bardziej złożone filtrowanie. Sprawdzanie ok. 2% wszystkich pakietów nawet przez setkę reguł w takiej sytuacji nie powinno rzucać cienia na wydajność zapory. Oczywiście wyniki te w dużej mierze zależne są od specyfiki sieci i usług, z których przeważnie się w niej korzysta.

Ponadto okazuje się, że zbiegiem okoliczności wcale nie powinno nam zależeć na szybkość przetwarzania nowych połączeń. Dobrym tego przykładem jest limitowanie pakietów TCP z ustawioną flagą SYN w celu ochrony przed atakami DoS, DDoS - czyli SYN flood. Tak więc w tej sytuacji czas jest po naszej stronie.

Optymalizacja zapory sprowadza się w znacznym stopniu do optymalnego wykorzystania mechanizmu stanu pakietów. Pokaże teraz przykład który moim zdaniem jest najoptymalniejszy. Najpierw jednak należy wymienić wszystkie stany jakie pakietowi mogą być przypisane:
  • NEW – pakiety, które tworzą nowe połączenie
  • ESTABLISHED – pakiety, które należą do nawiązanego połączenia
  • RELATED – pakiety tworzące nowe połączenie związane z już istniejącym połączeniem
  • INVALID – pakiety, które nie należą do żadnego nawiązanego połączenia lub poprawnego połączenia
Teraz przedstawię przykładową konfigurację optymalnej zapory dla łańcuch FORWARD jaką udało mi się opracować. Zapora została przetestowana na systemie testowym zainstalowanym na maszynie wirtualnej, który pracuje w roli routera dla drugiego systemu z maszyny wirtualnej. Zapora obejmuje tylko jedną usługę - HTTP (80/tcp) oraz niezbędny DNS (53/udp). Domyślna polityka łańcucha FORWARD ustawiona jest na DROP:

iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

iptables -A FORWARD -s 192.168.0.0/16 -p udp --dport 53 -m state --state NEW -j ACCEPT
iptables -A FORWARD -s 192.168.0.0/16 -p tcp --dport 80 --syn  -m state --state NEW -j ACCEPT

iptables -A FORWARD -p icmp -m state --state NEW -j ACCEPT
iptables -A FORWARD -m state --state INVALID  -j DROP
iptables -A FORWARD -j LOG --log-prefix "DROP_FORWARD " --log-tcp-options --log-ip-options --log-tcp-sequence

Jak widzimy ostatecznie co nie jest przepuszczane zostanie zalogowane. Bardzo interesujące jest to, w którym miejscu została wstawiona reguła dot. pakietów z ustawionym stanem INVALID. Michael Rash (twórca takich projektów jak psad oraz fwsnort) w swojej książce "Bezpieczeństwo sieci w Linuksie. Wykrywanie ataków i obrona przed nimi za pomocą iptables, psad i fwsnort" w przykładowej polityce bezpieczeństwa, która jest bardzo podobna do tej przedstawionej regułę dot. pakietów INVALID umieścił na samym początku. Powoduje to, że wszystkie ok. 97% pakietów należących do już nawiązanych połączeń musi jeszcze przejść przez tą regułę, podczas gdy z moich statystyk wynika, że ilość pakietów w stanie INVALID to tylko nieco ponad 1% całego ruch. Czy zatem konieczne jest dodawanie kolejnego warunku dla 97% aby odrzucić 1% niepoprawnych pakietów na samym początku zapory? Okazuje się że nie, i jest na to sposób, który przedstawiłem w zaprezentowanym przykładzie. Jest tylko jeden warunek, który należy spełnić aby nie wprowadzić reguły dot. pakietów w stanie INVALID na samym początku. Warunkiem tym jest to, że każda reguła musi korzystać z modułu stat i mieć przypisany jeden ze stanów.
Co mogło by się stać, gdyby tak nie było? Prosty przykład. Załóżmy, że w regule trzeciej dot. protokołu HTTP nie określiliśmy stanu NEW. Pakiet, który np. niespodziewanie zawiera flagę FIN, na dodatek jego numer sekwencyjny nie pasuje do prowadzonych sesji przez stos TCP/IP został by przepuszczony, ponieważ kierowany jest na port 80/tcp. Tylko wprowadzenie do reguły stanu NEW powstrzymuje taki pakiet przed dopasowaniem i zmuszenie go do dalszej drogi po regułach. Ponieważ pakietów tych jest nieco ponad 1% wypadałoby im przebyć najdłuższa drogę po regułach. W przedstawionym przykładzie tak też się dzieje. Ponieważ na początku szukamy pakietów ze stanem ESTABLISHED i RELATED więc pakiet INVALID nie może zostać dopasowany. Następnie możemy posiadać całą setkę reguł ze stanem NEW i pakiety ze stanem INVALID nadal nie mogą być dopasowane - zmuszamy je do dalszej drogi.  Ostatecznie pakiety nienależące do żadnego znanego połączenia trafiają na swoją regułę i dopiero tu zostają odrzucone. Dzięki temu pakiety ESTABLISHED oraz RELATED, które stanowią ok. 97% ruchu oszczędzają jeden warunek.
Teraz spójrzmy na wyniki prostej testowej zapory:

brama:~# iptables -L -vn --line-numbers
Chain INPUT (policy ACCEPT 24072 packets, 2033K bytes)
num   pkts bytes target     prot opt in     out     source               destination          

Chain FORWARD (policy DROP 79 packets, 4198 bytes)
num   pkts bytes target     prot opt in     out     source               destination        
1    15376 8512K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
2      653 40697 ACCEPT     udp  --  *      *       192.168.0.0/16       0.0.0.0/0           udp dpt:53 state NEW
3      497 24700 ACCEPT     tcp  --  *      *       192.168.0.0/16       0.0.0.0/0           tcp dpt:80 flags:0x17/0x02 state NEW    
4        2   120 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW          
5       12   480 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           state INVALID
6       79  4198 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0           LOG flags 7 level 4 prefix `DROP_FORWARD '

Chain OUTPUT (policy ACCEPT 17478 packets, 4444K bytes)
num   pkts bytes target     prot opt in     out     source               destination

Możemy tu zauważyć jak optymalna jest konfiguracja reguł. są ułożone w kolejności od najczęściej dopasowanych oraz najważniejszych do najmniej dopasowanych i najmniej ważnych. Żaden pakiet nie przybierze stanu, którego nie użyliśmy. Ponieważ każda z reguł posiada przypisany stan, to stan ten wyklucza wszystkie pozostałe zmuszając je do dalszej wędrówki po regułach w celu dopasowania. Wśród reguł dot. usług HTTP oraz DNS można zauważyć, że ta druga powinna mieć zdecydowanie wyższy priorytet niż pierwsza.
Ostatecznym celem było skonfigurowanie zapory w taki sposób, aby pakiety występujące najczęściej miały najkrótszą drogę do przebycia przez reguły w danym łańcuchu. Wygląda na to, że udano nam się zminimalizować tą drogę i tym samym zoptymalizować zaporę ogniową dzięki mechanizmowi filtrowania z pamięcią stanu.