Uwaga: Oryginalna wersja tego artykułu została opublikowana w IBM developerWorks i jest własnością Westtech Information Services. Poniższy dokument jest poprawioną przez zespół GDP wersją oryginalnego tekstu i nie jest już aktualizowany.

Dynamiczne zapory ogniowe iptables

Daniel Robbins  Autor
Mateusz Kotyrba  Tłumaczenie

Zaktualizowano 9 października 2005

1.  Wprowadzenie

Elastyczne (i zabawne) bezpieczeństwo sieci

Najlepszym sposobem zobaczenia korzyści wynikających z posiadania skryptów dynamicznej zapory ogniowej jest ich wypróbowanie. Aby to wykonać, wyobraźmy sobie najpierw, że jestem administratorem systemu u dostawcy internetowego i ostatnio założyłem nową zaporę ogniową opartą na Linuksie, aby chronić klientów oraz wewnętrzne systemy przed złośliwymi użytkownikami Internetu. Aby to wykonać moja zapora ogniowa używa nowego zestawu funkcji śledzenia stanu połączenia iptables Linuksa 2.4, aby zezwalać na połączenia wychodzące utworzone przez klientów sieci, serwery oraz oczywiście, aby zezwolić na nowe połączenia przychodzące, ale tylko do usług "publicznych", takich jak www, ftp, ssh oraz SMTP. Wszelkie połączenia z Internetu do usług niepublicznych, takich jak squid proxy cache lub serwer Samba, są automatycznie odrzucane, ponieważ użyłem wzoru deny-by-default (domyślnie odmów). Jak do tej pory posiadam dość przyzwoitą zaporę ogniową, która oferuje dobry poziom zabezpieczeń dla wszystkich klientów w mojej sieci.

Przez pierwszy tydzień zapora sprawuje się świetnie. Nagle dzieje się coś strasznego: Jaś, coś w rodzaju mojej osobistej nemesis (pracownik konkurencji) decyduje się na zatopienie mojej sieci pakietami, próbując tym samym zablokować dostęp do usług moim klientom. Niestety Jaś drobiazgowo przestudiował moją zaporę ogniową i wie, że chronię wiele usług wewnętrznych, ale porty 25 i 80 muszą być publicznie dostępne, po to abym mógł odbierać pocztę oraz obsługiwać żądania HTTP. Jaś decyduje się na skorzystanie z tego faktu poprzez przypuszczenie pożerającego przepustowość ataku przeciwko moim serwerom pocztowym i www.

Około minutę po tym jak Jaś rozpoczął swój atak, zauważam, że moje łącza nadrzędne są zasypywane pakietami. Po przyjrzeniu się bliżej sytuacji za pomocą tcpdump orientuję się, że to kolejny atak Jasia i zdobywam adresy IP których on używa. Teraz wszystko czego potrzebuję to blokada tych adresów IP, co powinno rozwiązać problem -- proste rozwiązanie, myślę.

Odpowiedź na atak

Szybko ładuję skrypt ustawiający moją zaporę ogniową do vi i zaczynam przerabiać moje reguły iptables, modyfikując zaporę, aby blokowała niechciane pakiety wysyłane przez Jasia. Po około minucie znajduję dokładne miejsce, gdzie mogę dodać kolejne reguły, więc je dodaję. Następnie zatrzymuję i uruchamiam zaporę ponownie.... ups, błąd przy dodawaniu reguł. Ładuję ponownie skrypty zapory, naprawiam i 30 sekund później zapora odpiera atak Jasia. Po pierwsze wygląda na to, że poradziłem sobie z atakiem.... dopóki nie zaczyna dzwonić telefon alarmowy. Najwyraźniej Jaś zdołał przerwać połączenie w mojej sieci na około 10 minut i teraz klienci dzwonią dowiedzieć się co się dzieje. Nawet gorzej, po kilku minutach znów zauważam, że łącza nadrzędne znów są zasypywane pakietami. Wygląda na to, że Jaś używa zupełnie nowych adresów IP. W odpowiedzi znów zaczynam gorączkowo przerabiać skrypty zapory, jednakże tym razem nieco panicznie -- możliwe, że moje rozwiązanie nie jest wcale takie dobre.

Oto co było nie tak w powyższej sytuacji. Pomimo że posiadałem przyzwoitą zaporę ogniową w odpowiednim miejscu oraz szybko zidentyfikowałem przyczynę problemu w sieci, nie byłem w stanie zmienić zachowania zapory, aby odpowiedziała na zagrożenie na czas. Oczywiście zawsze podczas ataku chcemy odpowiedzieć natychmiastowo, przerabianie ustawień skryptu głównej zapory w stanie paniki jest nie tylko stresujące, ale także bardzo mało wydajne.

2.  Skrypty

ipdrop

Byłoby znacznie lepiej gdybym posiadał specjalny skrypt ipdrop, który byłby zaprojektowany specjalnie do wklejania reguł blokujących wprowadzone adresy IP. Z takim skryptem blokada zapory ogniowej nie jest już kilkuminutową przeprawą, zamiast tego zajmuje to 5 sekund. Ponieważ skrypt chroni nas przed zadaniem ręcznej edycji reguł zapory, więc eliminuje główne źródło błędów. Wszystko co nam pozostaje to ustalenie adresów IP, które chcemy zablokować, po czym wpisujemy:

Listing 2.1: Dołączanie IP

# ipdrop 129.24.8.1 on
IP 129.24.8.1 drop on.

Natychmiastowo skrypt ipdrop zablokuje 129.24.8.1, złośliwy adres IP Jasia. Ten skrypt w ogromnym stopniu zwiększa naszą obronę, ponieważ teraz zablokowanie adresu IP nie wymaga już myślenia. Teraz przyjrzyjmy się skryptowi ipdrop:

Listing 2.2: Skrypt ipdrop

#!/bin/bash

source /usr/local/share/.sh

args 2 $# "${0} IPADDR {on/off}"

# Dołącza pakiety do (lub z) IPADDR. Dobre dla okropnych sieci/hostów/DoS"

if [ "$2" == "on" ]
then
# Reguły zostaną normalnie wklejone lub dołączone
 APPEND="-A"
  INSERT="-I"
  rec_check ipdrop $1 "$1 already blocked" on
  record ipdrop $1
elif [ "$2" == "off" ]
then
# Reguły zostaną usunięte
 APPEND="-D"
  INSERT="-D"
  rec_check ipdrop $1 "$1 not currently blocked" off
  unrecord ipdrop $1
else
  echo "Error: \"off\" or \"on\" expected as second argument"
  exit 1
fi

# Zestaw zewnętrznych adresów IP, które stwarzają problemy
# Nadchodzące połączenia TCP atakującego przeterminują się po około
# minucie, zmniejszanie skuteczności ataku DoS

iptables $INSERT INPUT   -s $1 -j DROP
iptables $INSERT OUTPUT  -d $1 -j DROP
iptables $INSERT FORWARD -d $1 -j DROP
iptables $INSERT FORWARD -s $1 -j DROP

echo "IP ${1} drop ${2}."

ipdrop: wyjaśnienie

Jeśli się przyjrzymy czterem ostatnim linijkom, to zobaczymy polecenia, które dołączają odpowiednie reguły do zapory ogniowej. Jak można zauważyć definicja zmiennej środowiskowej $INSERT różni się zależnie od tego czy wykonujemy operację "on" (wklejenia reguł) lub "off" (usunięcia reguł). Podczas wykonywania linii iptables, odpowiednie reguły są dodawane lub usuwane.

Przypatrzmy się teraz funkcjonowaniu samych reguł, które powinny działać perfekcyjnie z każdym rodzajem istniejących zapór lub nawet z systemem bez zapory ogniowej. Wszystko czego potrzebujemy to wsparcie dla iptables wkompilowane w nasze jądro 2.4. Blokujemy przychodzące pakiety nadchodzące ze złego IP (pierwsza linia iptables), blokujemy wychodzące pakiety w kierunku tegoż IP (następna linia iptables), po czym wyłączamy przekazywanie pakietów w obydwie strony dla tego szczególnego IP (dwie ostatnie linie iptables). Po ustaleniu reguł, system odrzuci wszystkie pakiety, które będą kwalifikowały się do jednej z tych kategorii.

Dodatkowa uwaga: możemy również zauważyć odwołania do "rec_check", "unrecord", "record" i "args". Są to specjalne funkcje pomocnicze basha zdefiniowane w dynfw.sh. Funkcja "record" zapisuje blokowany adres IP w pliku /root/.dynfw-ipdrop, podczas gdy "unrecord" usuwa wpis z /root/.dynfw-ipdrop. Funkcja "rec_check" jest używana do przerwania działania skryptu wyświetlając komunikat błędu jeśli próbujemy zablokować IP, który już jest blokowany lub odblokować IP który nie był blokowany. Funkcja "args" upewnia się, że dostajemy poprawną liczbę argumentów linii poleceń oraz zajmuje się wyświetlaniem pomocniczych informacji. Stworzyłem plik dynfw-1.0.1.tar.bz2, który zawiera wszystkie te narzędzia. Po więcej informacji proszę spojrzeć do części Zasoby, która znajduje się na końcu tego artykułu.

tcplimit

Następny skrypt dynamicznej zapory ogniowej jest użyteczny jeśli potrzebujemy ograniczyć wykorzystanie wybranej usługi, w sieci opartej o TCP, przykładowo takiej, która wytwarza duże obciążenie na CPU. Skrypt nazywa się "tcplimit", jako argumenty należy wpisać port TCP, szybkość transmisji, skalę czasu oraz "on" lub "off":

Listing 2.3: Ograniczanie wykorzystania usług sieci opartej na TCP

# tcplimit 873 5 minute on
Port 873 new connection limit (5/minute, burst=5) on.

tcplimit używa nowego modułu iptables "state" (należy się upewnić, że wkompilowaliśmy go w jądro lub posiadamy go jako załadowany moduł), aby zezwalać tylko na pewną liczbę nowych, połączeń przychodzących w pewnym przedziale czasu. W tym przykładzie zapora ogniowa zezwoli tylko na 5 nowych połączeń do serwera rsync (port 873) na minutę -- jest możliwe określenie żądanej liczby połączeń jakie chcielibyśmy mieć na sekundę/minutę/godzinę lub dzień.

tcplimit jest dobrym rozwiązaniem na ograniczanie zbędnych usług, aby ruch do zbędnej usługi nie spowodował przerwy w funkcjonowaniu sieci, bądź serwera. W naszym przypadku używamy tcplimit, aby ustawić górną granicę wykorzystania usługi rsync, aby zapobiec przeciążeniu linii DSL poprzez nasycenie łącza zbyt wieloma połączeniami rsync. Usługi ograniczone są wpisywane do pliku /root/.dynfw-tcplimit i jeśli kiedykolwiek będziemy chcieli porzucić to ograniczenie, to po prostu wpiszemy:

Listing 2.4: Wyłączanie ograniczania połączeń

# tcplimit 873 5 minute off
Port 873 new connection limit off.

tcplimit tworzy zupełnie nowy ciąg w tablicy "filtrów". Ten ciąg odrzuci wszystkie pakiety, które przekroczą określone ograniczenie. Wtedy pojedyncza reguła jest wklejana do ciągu INPUT, który przekierowywuje wszystkie NOWE pakiety połączeń przychodzących to do portu docelowego (w tym przypadku 873) do tego specjalnego ciągu, skutecznie ograniczając nowe połączenia przychodzące, nie mając wpływu na pakiety, które są częścią już ustanowionego połączenia.

Kiedy tcplimit jest wyłączony, wtedy reguła INPUT oraz specjalny ciąg są usuwane. To jest ten rodzaj wymyślnych rzeczy, który naprawdę podświetla ważność posiadania dobrze przetestowanych, na których w każdej chwili możemy polegać, skryptów zarządzającymi naszą zaporą. Tak jak w przypadku ipblock, skrypt tcplimit powinien być zgodny z każdym rodzajem zapory lub nawet z brakiem zapory, pod warunkiem że mamy załączone iptables w jądrze.

host-tcplimit

host-tcplimit w dużej mierze jest podobny do tcplimit, ale ogranicza nowe połączenia TCP nadchodzące z konkretnego adresu IP zmierzające do konkretnego portu TCP na naszym serwerze. host-tcplimit jest szczególnie użyteczny podczas powstrzymywania konkretnej osoby przed nadużyciem zasobów naszej sieci. Dla przykładu, powiedzmy, że mamy serwer CVS i odkrywamy, że nowy deweloper napisał sobie skrypt, który aktualizuje jego źródła z tymi z repozytorium co 10 minut, wykorzystując do tego celu ogromną ilość niepotrzebnych zasobów sieciowych przez cały dzień. Jednakże w trakcie pisania do niego listu e-mail tłumacząc jego błędne działania, dostajemy następującą wiadomość:

Listing 2.5: Nadchodząca wiadomość

Cześć chłopaki!

Jestem bardzo podekscytowany tym, że jestem uczestnikiem projektu
deweloperskiego. Właśnie napisałem nowy skrypt, który będzie aktualizował moje
źródła co 10 minut. Mam zamiar wyjechać na 2 tygodnie, ale gdy wrócę moje
źródła będą całkowicie aktualne i będę gotów do pomocy! Już wychodzę za
próg.... do zobaczenia za 2 tygodnie!

Pozdrawiam,

Nowicjusz

W takich sytuacjach proste polecenie host-tcplimit rozwiąże nasz problem:

Listing 2.6: Polecenie host-tcplimit

# host-tcplimit 1.1.1.1 2401 1 day on

Teraz pan Nowicjusz (o adresie IP 1.1.1.1) jest ograniczony do 1 połączenia z repozytorium CVS (port 2401) w ciągu dnia, oszczędzając tym samym mnóstwo zasobów sieciowych.

user-outblock

Ostatnim i być może najbardziej intrygującym ze wszystkich skryptów dynamicznych zapór ogniowych jest user-outblock. Ten skrypt dostarcza idealny sposób do zezwolenia konkretnemu użytkownikowi na połączenie telnet lub ssh do naszego systemu, ale nie pozwalając tym samym na ustanowienie jakichkolwiek innych nowych połączeń wychodzących z wiersza poleceń. Przedstawimy sytuację gdzie skrypt user-outblock się przydaje. Powiedzmy, że pewna rodzina posiada konto w mojej sieci. Mama i tata do odczytu poczty używają graficznego klienta e-mail i czasem przeglądają Internet, lecz wygląda na to, że ich syn jest aspirującym hakerem i używa powłoki do robienia psikusów na komputerach innych ludzi.

Pewnego dnia widzimy, że ustanowił on połączenia ssh z kilkoma systemami, które mogły przynależeć do pakistańskiej armii, ups. Chcielibyśmy skierować tego młodzieńca do bardziej pożytecznych zajęć, więc wykonujemy:

Po pierwsze sprawdzamy nasz system upewniając się, że usunęliśmy bit suid z wszystkich sieciowych plików binarnych, jak ssh:U.

Listing 2.7: Usuwanie bitu suid z wszystkich sieciowych plików binarnych

# chmod u-s /usr/bin/ssh

Teraz każdy proces, którego on spróbuje użyć do współdziałania z siecią, będzie własnością jego UID. Teraz możemy użyć user-outblock, aby zablokować wszystkie wychodzące połączenia TCP zainicjowane przez to UID (w naszym przypadku jest to 2049):

Listing 2.8: Blokowanie wszystkich wychodzących połączeń TCP zainicjowanych przez pewne UID

# user-outblock 2049 on
UID 2049 block on.

Teraz może on logować się i czytać swoją pocztę, ale nie będzie mógł używać naszych serwerów do ustanawiania połączeń ssh lub podobnych. Może zainstalować klienta ssh na swoim komputerze. Jednakże nie jest trudne wywołanie kolejnego skryptu dynamicznej zapory, który będzie ograniczał mu dostęp do www, poczty, wychodzących połączeń ssh (tylko do naszych serwerów).

3.  Zasoby

Tarballe

Ponieważ skrypty dynamicznej zapory ogniowej są aż tak pomocne, więc spakowałem wszystko do jednego pliku tar ( dynfw-1.0.tar.gz), który można rozpakować i zainstalować to co się w nim znajduje.

Aby móc zainstalować zawartość tarballa, najpierw trzeba wypakować jego zawartość, po czym wykonujemy zawarty skrypt install.sh. Ten skrypt zainstaluje współdzielony skrypt basha do /usr/local/share/dynfw.sh oraz zainstaluje same skrypty dynamicznej zapory ogniowej do /usr/local/sbin. Jeśli zamiast tego chcielibyśmy, aby znalazły się one w /usr/share i /usr/sbin, po prostu przed uruchomieniem install.sh wpisujemy to:

Listing 3.1: Eksportowanie lokalizacji katalogu instalacyjnego

# export PREFIX=/usr

Dodałem także stronę skryptów dynamicznej zapory ogniowej do strony Gentoo Linux, którą możemy odwiedzić i ściągnąć najnowszą wersję tarballa. Chciałbym kontynuować ulepszanie oraz dodawanie do tego zbioru, czyniąc go naprawdę użytecznym źródłem dla administratorów systemów z całego świata. Teraz kiedy już mamy iptables w jądrze, nadszedł czas, aby z tego skorzystać!

Jeśli wszystko co związane z iptables jest dla nas nowe, wtedy gorąco polecam moje Wprowadzenie do zapór ogniowych ze śledzeniem stanu w Linuksie 2.4 (wymagana rejestracja), zawierający pełen spis instrukcji pomocnych do zaprojektowania własnej zapory ogniowej ze śledzeniem stanu opartej na iptables.

tcpdump jest niezbędnym narzędziem do zgłębiania wymian pakietów niskiego poziomu (ang. low-level packet exchanges) oraz do weryfikacji, że nasza zapora działa prawidłowo. Jeśli go jeszcze nie mamy, wtedy koniecznie musimy go zdobyć. Jeśli go używamy, to... dobrze dla nas :).

Na stronie domowej zespołu netfilter znajdziemy wiele zasobów, włączając źródła iptables oraz znakomite nierzetelne przewodniki, których autorem jest Rusty. Zawierają one przewodnik po podstawowych pojęciach pracy w sieci, przewodnik netfilter (iptables), przewodnik NAT oraz przewodnik przerabiania netfilter dla deweloperów. Jest również dostępny zbiór Najczęstszych pytań i odpowiedzi o netfilter oraz wiele innych rzeczy.

Na szczęście w Internecie istnieje wiele dobrych zasobów o netfilter; jednakże nie należy zapomnieć o podstawach. Strona programu man iptables jest bardzo szczegółowa i jest świecącym przykładem jak powinna wyglądać strona dokumentacji systemowej.

Jest również dostępny Advanced Linux Routing and Traffic Control HOWTO. W którym jest bardzo dobra część, która pokazuje użycie iptables do znaczenia pakietów i użycia zespołu funkcji rutowania Linuksa do wyboru drogi pakietów na podstawie tamtych śladów.

Istnieje również lista dyskusyjna netfilter (iptables) oraz lista dyskusyjna dla deweloperów netfilter . Pod podanymi adresami URL mamy również dostęp do archiwów tych list.