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
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}"
if [ "$2" == "on" ]
then
APPEND="-A"
INSERT="-I"
rec_check ipdrop $1 "$1 already blocked" on
record ipdrop $1
elif [ "$2" == "off" ]
then
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
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.
|