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. |
W artykule omówimy użycie netfilter do skonfigurowania skutecznego firewalla pełnostanowego pod Linuksem. Wystarczy działający system Linux z jądrem 2.4. Może to być laptop, stacja robocza, router lub serwer z Linuksem 2.4.
Niezbędna jest pewna znajomość standardowej terminologii dotyczącej problematyki sieciowej, takiej jak adresy IP, numery portów źródłowych i docelowych, TCP, UDP, ICMP, etc. Po lekturze tego artykułu stanie się jasne, jak są skonstruowane linuksowe pełnostanowe zapory sieciowe. Pokazane również zostanie kilka przykładowych konfiguracji.
Z technicznymi pytaniami dotyczącymi zawartości tego podręcznika należy się zwracać do autora, Daniela Robbinsa, na adres drobbins@gentoo.org.
Mieszkający w Albuqerque, w Nowym Meksyku, Daniel Robbins był Głównym Architektem Gentoo Technologies Inc., twórcą Gentoo Linux, zaawansowanej dystrybucji dla komputerów PC. Stworzył również Portage, nowej generacji system portów programowych dla Linuksa. Uczestniczył także w zespołach redakcyjnych wydawnictwa Macmillan piszących książki takie jak "Caldera OpenLinux Unleashed", "SuSE Linux Unleashed" oraz "Samba Unleashed". Daniel zainteresował się komputerami w szkole średniej, kiedy los zetknął go z językiem programowania Logo, jak również został wystawiony na potencjalnie niebezpieczną dawkę PacMana. To prawdopodobnie wyjaśnia, czemu później pracował jako grafik w SONY Electronic Publishing/Psygnosis. Daniel najbardziej lubi spędzać czas ze swoją żoną Mary i nowo narodzoną córką Hadassah.
Ten artykuł ma pokazać, jak zainstalować firewall pełnostanowy (zwany inaczej "stanowym") pod Linuksem. Powinien on działać na laptopie, stacji roboczej, serwerze lub routerze. Jego podstawowym zadaniem ma być przepuszczenie wyłącznie ruchu sieciowego konkretnego typu. Dla zwiększenia bezpieczeństwa, firewall ten zostanie tak skonfigurowany, aby upuszczać (DROP) lub odrzucać (REJECT) zarówno takie typy pakietów, które nie są potrzebne, jak również takie, które mogą stanowić zagrożenie dla bezpieczeństwa.
Zanim rozpoczniemy konstrukcję firewalla, będziemy potrzebować dwóch rzeczy. Po pierwsze należy się upewnić, że możemy wywołać komendę iptables. Należy napisać iptables jako root i zobaczyć czy program istnieje. Jeśli nie, trzeba będzie go najpierw zainstalować w poniższy sposób:
Listing 2.1: Instalowanie potrzebnych narzędzi |
# emerge iptables
|
Po zainstalowaniu, komenda iptables powinna być dostępna, utworzona zostanie również strona manuala (man iptables). Teraz pozostaje się upewnić, że mamy niezbędne funkcjonalności wbudowane w jądro. Ten podręcznik wymaga umiejętności samodzielnej kompilacji kernela. Należy wejść do /usr/src/linux i wpisać make menuconfig lub make xconfig. Będziemy uruchamiać pewne funkcje sieciowe jądra.
W sekcji "Networking options" należy włączyć przynajmniej poniższe opcje:
Listing 2.2: Niezbędne opcje w kernelu |
<*> Packet socket [*] Network packet filtering (replaces ipchains) <*> Unix domain sockets [*] TCP/IP networking [*] IP: advanced router [*] IP: policy routing [*] IP: use netfilter MARK value as routing key [*] IP: fast network address translation [*] IP: use TOS value as routing key |
Następnie pod menu "IP: Netfilter Configuration ->" należy włączyć wszystkie opcje, co zapewni pełną funkcjonalność zapory. Nie wszystkich jej możliwości użyjemy, ale dobrze mieć je włączone do przyszłych eksperymentów.
Jednej opcji w kategorii "Networking options" nie należy włączać: explicit congestion notification. Należy pozostawić ją wyłączoną.
Listing 2.3: Opcja do wyłączenia |
[ ] IP: TCP Explicit Congestion Notification support |
Gdyby ta opcja została włączona, komputer z Linuksem nie mógłby uzyskać dostępu do 8% zasobów Internetu. Mając włączoną opcję ECN, Linux wysyła niektóre pakiety z ustawionym bitem ECN, chociaż to powoduje kłopoty z niektórymi internetowymi routerami. Zatem wyłączenie tej opcji bardzo ważne. Jeśli opcja ta nie jest w ogóle dostępna w jądrze, można zignorować te ostrzeżenia.
Po wszystkim będziemy mieli kernel skonfigurowany zgodnie z naszymi potrzebami. Teraz trzeba go skompilować, zainstalować i ponownie wystartować system. Pora na zabawę z netfilter. :)
Naszym najlepszym przyjacielem przy konstrukcji zapory jest komenda iptables. Używa się jej do interakcji z regułami filtrowania pakietów w jądrze systemu. Będziemy używać komendy iptables do tworzenia nowych reguł, wyświetlania już istniejących, usuwania ich oraz ustawiania domyślnej polityki zarządzania pakietami. Oznacza to, że aby zbudować firewall, trzeba będzie wprowadzić serię komend iptables. I temu się przyjrzymy najpierw (na razie nie radzimy wpisywać żadnych poleceń, tylko po prostu czytać dalej).
Listing 2.4: Zmiana polityki łańcuchów na DROP |
# iptables -P INPUT DROP
|
Powyżej widać prawie "doskonały" firewall. Jeżeli wprowadzimy tę komendę, będziemy niewiarygodnie dobrze chronieni przed jakimkolwiek złośliwym atakiem. A wszystko dlatego, że każe ona kernelowi odrzucić każdy przychodzący pakiet. Chociaż taki firewall jest krańcowo bezpieczny, ale rozwiązanie to nie należy do najmądrzejszych. Zanim przejdziemy dalej, popatrzmy jak dokładnie działa ta komenda.
Komendy iptables można użyć do ustawienia domyślnej polityki dla łańcucha reguł filtrowania pakietów. W tym przykładzie, iptables zostanie użyta do ustanowienia domyślnej polityki dla INPUT, wbudowanego łańcucha reguł mających zastosowanie do każdego przychodzącego pakietu. Ustawiając domyślną politykę na DROP, przekazujemy do jądra systemu, że każdy pakiet, który osiągnie koniec łańcucha reguł INPUT, ma zostać upuszczony (czyli zignorowany). Zatem, ponieważ nie dodaliśmy żadnej innej reguły do łańcucha INPUT, wszystkie pakiety osiągają koniec łańcucha i zostają upuszczone.
Oczywiście ta komenda jako taka jest kompletnie bezużyteczna, aczkolwiek ukazuje dobrą strategię tworzenia firewalla. Zaczynamy od domyślnego upuszczania wszystkich pakietów, a następnie stopniowo otwieramy zaporę zgodnie z naszymi potrzebami. To uczyni ją możliwie szczelną i bezpieczną.
Na użytek tego przykładu przyjmiemy, że tworzymy firewall dla komputera z dwoma interfejsami sieciowymi, eth0 i eth1. Karta eth0 jest podłączona do naszej sieci LAN, natomiast karta eth1 do routera DSL, łącza z Internetem. W związku z tym poprawimy naszą "najdoskonalszą zaporę ogniową" przez dodanie jednej linii:
Listing 3.1: Poprawianie najdoskonalszej zapory ogniowej |
# iptables -P INPUT DROP # iptables -A INPUT -i ! eth1 -j ACCEPT |
Kolejna linia, iptables -A, dodała nową regułę filtrowania pakietów na koniec łańcucha INPUT. Teraz nasz łańcuch INPUT zawiera pojedynczą regułę oraz domyślną politykę "upuść". Popatrzmy zatem, co teraz potrafi nasz prawie ukończony firewall.
Kiedy do dowolnego interfejsu sieciowego (lo, eth0 lub eth1) przychodzi pakiet, kod netfilter kieruje go do łańcucha INPUT i porównuje czy pasuje on do pierwszej reguły. Jeżeli tak, pakiet zostaje zaakceptowany i nie podlega dalszemu przetwarzaniu. Jeżeli nie, zostaje zastosowana domyślna polityka i pakiet jest upuszczany (ignorowany).
Tak to wygląda ogólnie. Patrząc dokładniej, nasza pierwsza reguła pasuje do wszystkich pakietów przychodzących poprzez kartę eth0 oraz interfejs lo i niezwłocznie je przepuszcza. Natomiast każdy pakiet przychodzący poprzez eth1 zostanie odrzucony. Zatem jeżeli włączymy ten firewall na naszej maszynie, będzie ona mogła komunikować się z naszą siecią LAN, ale zostanie całkowicie odcięta od Internetu. Przyjrzyjmy się kilku metodom otwarcia ruchu internetowego.
Oczywiście, aby nasza zapora stała się użyteczna, niektóre, wybrane pakiety z Internetu powinny zostać dopuszczone do naszej maszyny. Istnieją dwa podejścia do otwierania naszej zapory do użytecznego poziomu: jedna korzysta ze statycznych reguł, druga używa reguł dynamicznych, pełnostanowych.
Przyjrzyjmy się na przykład pobieraniu strony internetowej. Jeżeli chcemy, aby nasza maszyna pobierała strony internetowe, możemy dodać regułę statyczną, która zawsze będzie pasować do każdego przychodzącego pakietu http, niezależnie od źródła pochodzenia:
Listing 3.2: Akceptowanie wszystkich przychodzących pakietów http |
# iptables -A INPUT --sport 80 -j ACCEPT
|
Ponieważ cały standardowy ruch http przychodzi z portu 80, reguła ta efektywnie dozwoli naszemu komputerowi pobierać strony www. Jednakowoż, takie tradycyjne podejście, krańcowo akceptowalne, sprawia kilka problemów.
Oto problem: wprawdzie nie cały, ale większość ruchu internetowego pochodzi z portu 80. Zatem, o ile powyższa reguła zadziała w większości prawidłowo, w niektórych wypadkach zaszkodzi. Na przykład, zapewne widzieliście URL, który wygląda tak: "http://www.foo.com:81". Ten adres wskazuje na stronę internetową dostępną na porcie 81, zamiast domyślnego 80. Zatem będzie niedostępny spoza naszej aktualnej zapory ogniowej. Uznawanie wszystkich takich nietypowych wypadków szybko zamieni naszą superbezpieczną ścianę ogniową w ser szwajcarski i wypełni łańcuch INPUT masą reguł do zastosowania dla bardzo rzadkich [oddball] stron www.
Jednakowoż najważniejszy problem tej reguły związany jest z bezpieczeństwem. Oczywiście to prawda, że tylko ruch z portu 80 zostanie przepuszczony przez firewall, lecz port pochodzenia (source port) pakietu to nie to, co powinniśmy kontrolować, jako że może on być łatwo zmieniany przez atakującego. Na przykład jeżeli atakujący wie jak firewall został skonstruowany, może go obejść, wystarczy, że wszystkie połączenia przychodzące z jego maszyny będą pochodzić z portu 80! Skoro statyczna reguła zapory jest tak łatwa do pokonania, potrzebujemy bezpieczniejszego, dynamicznego podejścia. Szczęśliwie, iptables i kernel 2.4 zawierają wszystko, co potrzebne do uruchomienia dynamicznego, pełnostanowego filtrowania.
Zamiast otwierać dziury w oparciu o statyczne charakterystyki protokołu w naszej zaporze ogniowej, możemy użyć nowej linuksowej funkcjonalności śledzenia (tracking), aby decyzje firewalla opierały się o dynamiczny stan połączenia pakietów. Conntrack działa poprzez podłączanie każdego pakietu do indywidualnego, dwukierunkowego kanału komunikacyjnego lub połączenia.
Sprawdźmy na przykład co się stanie, kiedy użyjemy telnetu lub ssh, aby podłączyć się zdalnie do oddalonego komputera. Jeżeli spojrzymy na sieć z poziomu pakietu, wszystko, co zobaczymy, to masa pakietów sunąca z jednej maszyny do drugiej. Na wyższym poziomie tymczasem, ta wymiana pakietów stanie się w rzeczywistości dwukierunkowym kanałem komunikacyjnym pomiędzy naszym komputerem a drugim, znajdującym się gdzieś w sieci. Tradycyjne (przestarzałe i niemodne) zapory sieciowe analizują każdy pakiet indywidualnie i nie rozpoznają, że są one naprawdę częścią większej całości - połączenia.
W tym miejscu wkracza technika śledzenia. Linuksowa funkcjonalność conntrack może "widzieć" wyższy poziom odbywających się połączeń, gdyż rozpoznaje sesję ssh jako pojedynczą jednostkę logiczną. Conntrack może nawet rozpoznać wymianę pakietów ICMP i UDP jako logiczne "połączenia", nawet pomimo faktu, że UDP i ICMP z natury swojej nie tworzą połączeń. Jest to bardzo użyteczne, jako że pozwala zarządzać wymianą pakietów ICMP i UDP.
Jeżeli już zrestartowaliśmy i uruchomiliśmy jądro systemu z włączonym netfilterem, możemy zobaczyć aktywne połączenia sieciowe na naszej maszynie poprzez wpisanie cat /proc/net/ip_conntrack. Nawet bez skonfigurowanej zapory, funkcja conntrack działa w tle, podejmując śledzenie połączeń w których uczestniczy nasz komputer.
Conntrack nie tylko rozpoznaje połączenia, również klasyfikuje każdy widziany pakiet do jednego z czterech stanów połączenia. Pierwszy stan, o którym będziemy mówić, nazywany jest NOWYM (NEW). Jeżeli napisać ssh remote.host.com, pakiet początkowy lub seria pakietów pochodzących z naszej maszyny, a skierowanych do remote.host.com znajdą się w stanie NEW. Jednak dopóki nie otrzymamy choćby pojedynczego pakietu odpowiedzi od remote.host.com, żaden następny pakiet wysłany do remote.host.com jako część tego samego połączenia nie zostanie zaliczony do stanu NEW. Zatem pakiety mogą zostać uznane do stanu NEW tylko jeżeli służą do nawiązania nowego połączenia i żaden ruch zwrotny ze zdalnego hosta (jako część danego połączenia oczywiście) jeszcze się nie pojawił.
Opisane tu zostały pakiety wychodzące stanu NEW, ale przecież często się zdarzy mieć w stanie NEW pakiety przychodzące. Takie pakiety generalnie pochodzą z oddalonej maszyny i służą do nawiązania połączenia z nami. Pakiety początkowe, które otrzymuje nasz Web serwer jako część zapytania HTTP, zostaną zaliczone do stanu NEW. Jednak kiedy tylko odpowiemy na choćby jeden przychodzący pakiet, żaden następny związany z tym konkretnym połączeniem nie zostanie zaliczony do stanu NEW.
Kiedy tylko zostanie nawiązane połączenie w obu kierunkach, następne pakiety związane z tym połączeniem zostaną zaliczone do stanu USTANOWIONE (ESTABLISHED). Stany NEW i ESTABLISHED istotnie się różnią, jak za chwilę zobaczymy.
Trzecia kategoria stanu połączenia nazywana jest POWIĄZANY (RELATED). Pakietami POWIĄZANYMI nazywamy takie, które nawiązują nowe połączenie, ale w powiązaniu do innego, już istniejącego. Stan RELATED może zostać użyty do regulowania połączeń, które stanowią część protokołu wielopołączeniowego (multi-connection) jak FTP lub dla pakietów błędów (error packets) powiązanych z istniejącymi połączeniami (jak komunikaty błędów ICMP powiązane z istniejącym połączeniem).
W końcu mamy również pakiety BŁĘDNE (INVALID) - takie, które nie mogą zostać zaklasyfikowane do żadnej z trzech powyższych kategorii. Należy zwrócić uwagę, że pakiety uznane za INVALID, nie zostają automatycznie usunięte. Właściwe nimi operowanie zależy od twórcy firewalla, do którego należy ustanowienie odpowiedniej reguły i łańcucha polityki.
Dodawanie reguły pełnostanowej.
W porządku, zatem rozumiemy już zasady śledzenia połączeń, czas na to, aby przyjrzeć się jednej dodatkowej regule, która zamieni nasz niefunkcjonalny firewall w coś całkiem użytecznego:
Listing 4.1: Dodawanie reguły pełnostanowej. |
# iptables -P INPUT DROP # iptables -A INPUT -i ! eth1 -j ACCEPT # iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT |
Kiedy ta pojedyncza reguła zostanie dodana do istniejącego łańcucha INPUT, przyzwoli na ustanawianie połączeń z oddalonymi maszynami. Działa następująco: Powiedzmy, że chcemy połączyć się przy pomocy ssh do remote.host.com. Po wpisaniu ssh remote.host.com, nasz komputer wysyła pakiet do rozpoczęcia połączenia. Ten pojedynczy pakiet znajduje się w stanie NEW i nasz firewall wypuści go, ponieważ blokujemy wyłącznie pakiety przychodzące do naszego firewalla, a nie opuszczające go.
Kiedy przyjdzie pakiet z odpowiedzią od remote.host.com, przechodzi przez nasz łańcuch INPUT. Nie pasuje do pierwszej reguły (skoro przychodzi na eth1), więc idzie do naszej następnej, finalnej reguły. Jeżeli do niej pasuje, zostanie zaakceptowany, jeżeli nie, spadnie na koniec łańcucha INPUT i zostanie zastosowana domyślna polityka (DROP). Zatem czy ten pakiet z odpowiedzią zostanie zaakceptowany czy upuszczony?
Odpowiedź: zostanie zaakceptowany. Kiedy kernel sprawdza przychodzący pakiet, po pierwsze rozpoznaje czy jest częścią istniejącego połączenia. Następnie musi zdecydować czy jest to pakiet stanu NEW czy ESTABLISHED. Skoro jest to pakiet przychodzący, jądro sprawdza czy w tym połączeniu wystąpił już jakiś ruch wychodzący i stwierdza, że tak było (nasz początkowy pakiet stanu NEW, który został już wysłany). Zatem ten przychodzący pakiet zostanie zaliczony do stanu ESTABLISHED, jak i wszystkie następne pakiety, które otrzymamy lub wyślemy w ramach tego połączenia.
Rozważmy teraz co się stanie, jeżeli ktoś spróbuje połączyć się z nami przez ssh ze zdalnego komputera. Otrzymany pakiet początkowy zostanie zakwalifikowany jako NEW, nie pasuje do reguły 1, więc przechodzi do reguły 2. Ponieważ nie znajduje się w stanie ESTABLISHED lub RELATED, spada na koniec łańcucha INPUT i zostaje zastosowana domyślna polityka DROP. Przychodzące zapytanie o połączenie ssh zostaje spuszczone po linie bez najmniejszej odpowiedzi z naszej strony (lub komunikatu TCP reset).
Zatem jakiego firewalla już się dorobiliśmy? Doskonałego dla laptopa lub stacji roboczej, gdzie nie życzymy sobie połączeń przychodzących z Internetu, ale kiedy sami potrzebujemy połączyć się z jakąś stroną lub usługą, drzwi są otwarte. Będziemy mogli używać Netscape, konquerora, ftp, ping, wykonywać DNS lookups i wiele innych rzeczy. Każde połączenie zainicjowane przez nas przedostanie się przez firewall. Natomiast jakiekolwiek połączenie samowolnie dobijające się do nas z Internetu zostanie odrzucone, chyba że jest powiązane z połączeniem zainicjowanym przez nas. Dopóki nie prowadzimy żadnych usług sieciowych, mamy prawie że doskonałą zaporę ogniową.
Podstawowy skrypt zapory ogniowej
Poniżej znajduje się prosty skrypt, który może zostać użyty do ustanowienia lub zburzenia naszego pierwszego, podstawowego firewalla dla stacji roboczej:
Listing 4.2: Podstawowy skrypt zapory ogniowej. |
#!/bin/bash # A basic stateful firewall for a workstation or laptop that isn't running any # network services like a web server, SMTP server, ftp server, etc. if [ "$1" = "start" ] then echo "Starting firewall..." iptables -P INPUT DROP iptables -A INPUT -i ! eth1 -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT elif [ "$1" = "stop" ] then echo "Stopping firewall..." iptables -F INPUT iptables -P INPUT ACCEPT fi |
Wyłączenie zapory nastąpi po wpisaniu ./firewall stop, ponowne włączenie poprzez wpisanie ./firewall start. Aby wyłączyć firewalla opróżniamy nasz łańcuch INPUT z reguł poprzez iptables -F INPUT, następnie przełączamy domyślną politykę na ACCEPT poprzez komendę iptables -P INPUT ACCEPT. Przyjrzyjmy się teraz garści ulepszeń, które możemy wprowadzić do naszej już działającej zapory. Po tym, jak już objaśnimy każdą poprawkę, zobaczymy pełny skrypt zapory i przystąpimy do rozwijania go pod kątem zastosowań serwerowych.
Jak już zauważyliśmy wcześniej, należy koniecznie wyłączyć ECN (explicit congestion notification), aby nasze połączenia internetowe działały poprawnie. Chociaż może i wyłączyliście ECN w jądrze systemu po mojej sugestii, w przyszłości możecie o tym zapomnieć. Możliwe też, że prześlecie skrypt firewall komuś, kto ma włączony ECN. Dlatego wydaje się sensownym pomysłem użycie interfejsu /proc, aby całkowicie wyłączyć ECN:
Listing 5.1: Całkowicie wyłączyć ECN. |
if [ -e /proc/sys/net/ipv4/tcp_ecn ]
then
echo 0 > /proc/sys/net/ipv4/tcp_ecn
fi
|
Jeżeli używamy naszego komputera z Linuksem jako routera, możemy zechcieć włączyć przekazywanie pakietów IP, co pozwoli kernelowi przepuszczać pakiety pomiędzy eth0 a eth1 i z powrotem. W naszej przykładowej konfiguracji, gdzie karta eth0 została podłączona do naszej sieci LAN, a eth1 do Internetu, włączenie przekazywania pakietów IP stanowi krok niezbędny, aby pozwolić sieci LAN komunikować się z Internetem przez nasz komputer z Linuksem. Aby włączyć przekazywanie pakietów IP, użyjcie poniższej linijki:
Listing 5.2: Przekazywanie pakietów. |
# echo 1 > /proc/sys/net/ipv4/ip_forward
|
Dotychczas odrzucaliśmy cały samowolny ruch przychodzący z Internetu. Chociaż jest to dobry sposób na trzymanie z dala niechcianych działań z sieci, ma również kilka wad. Największym problemem takiego podejścia jest łatwość, z jaką atakujący może wykryć, że używamy firewalla, skoro nasza maszyna nie odpowiada standardowymi komunikatami TCP reset i ICMP port-unreachable - a które normalna maszyna powinna odsyłać jako znak nieudanego połączenia lub braku oczekiwanej usługi.
Zamiast dać atakującemu znać, że używamy firewalla (i w ten sposób zasugerować mu, że może za nim ukrywamy jakieś cenne usługi, do których warto się dobrać), korzystniej dla nas będzie twierdzić, że nie mamy żadnych działających usług. Uzyskamy to poprzez dodanie poniższych dwóch regułek na końcu naszego łańcucha INPUT:
Listing 5.3: Przygotowanie odrzuceń. |
# iptables -A INPUT -p tcp -i eth1 -j REJECT --reject-with tcp-reset # iptables -A INPUT -p udp -i eth1 -j REJECT --reject-with icmp-port-unreachable |
Pierwsza reguła zajmuje się poprawnym odstrzeliwaniem połączeń TCP, podczas gdy druga odpowiada za UDP. Wobec tych dwóch reguł intruzowi bardzo trudno będzie wykryć, że w rzeczywistości korzystamy z firewalla. Przy pewnej dozie szczęścia atakujący odpuści sobie naszą maszynę i poszuka innych celów, łatwiejszych do upolowania.
Oprócz uczynienia naszej zapory trudniejszą do wykrycia, regułki te również eliminują opóźnienia związane z podłączaniem się do konkretnych serwerów ftp i irc. Opóźnienia te spowodowane są przez serwer wykonujący ident lookup do naszej maszyny (podłączenie na porcie 113) i rozłączający się dopiero po ok. 15 sekundach. Teraz nasz firewall zwróci komunikat TCP reset i ident lookup zakończy się niezwłocznie zamiast powtarzać się przez 15 sekund (podczas kiedy my cierpliwie będziemy czekać na odpowiedź serwera).
Ochrona przed podszywaniem się (spoofingiem)
W wielu dystrybucjach, kiedy interfejsy sieciowe włączą się, zostaje dodane do systemu kilka starych reguł ipchains. Reguły te zostały stworzone przez autorów dystrybucji, aby poradzić sobie z problemem zwanym "spoofing". Polega on na tym, że źródłowy adres pakietów zostaje podmieniony, przez co zawierają one nieprawdziwą wartość (script-kiddies często się tym posługują). Chociaż możemy utworzyć podobne reguły iptables blokujące podrobione pakiety, jest na to łatwiejszy sposób. Współczesne jądro systemu ma wbudowaną zdolność odrzucania podrobionych pakietów. Wszystko, co trzeba zrobić, to uruchomić to przez prosty interfejs /proc.
Listing 5.4: Ochrona przed podszywaniem się (spoofingiem). |
for x in lo eth0 eth1
do
echo 1 > /proc/sys/net/ipv4/conf/${x}/rp_filter
done
|
Ten skrypt shellowy nakaże kernelowi odrzucić wszystkie podrobione pakiety na interfejsach lo, eth0 i eth1. Możemy również dodać te linijki do naszego skryptu firewalla lub do skryptu włączającego nasze interfejsy lo, eth0 i eth1.
Translacja Adresów Sieciowych - NAT (network address translation) oraz maskowanie IP (IP masquerading), chociaż nie do końca związane z ideą zapory ogniowej, często są używane razem z nią. Przyjrzymy się tutaj dwóm popularnym konfiguracjom NAT i Maskarady, których moglibyśmy potrzebować. Pierwsza reguła zajmuje się sytuacjami, w których mamy połączenie wdzwaniane (ppp0) do Internetu, w którym zastosowane jest dynamiczne przyznawanie numeru IP:
Listing 5.5: Maskarada (Masquerading) |
# iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
|
Jeżeli mamy taką sytuację, możemy również zmienić te fragmenty skryptu, które odwołują się do "eth1" (nasz przykładowy router DSL) na "ppp0". Nie ma także problemu z dodawaniem reguł dotyczących interfejsu "ppp0", chociaż takiego na razie nie mamy. Kiedy tylko zostanie włączony, wszystko zadziała doskonale. Należy się również upewnić, że również przekazywanie IP zostało włączone.
Jeżeli do połączeń internetowych używamy DSL, mamy prawdopodobnie jedną lub dwie możliwe konfiguracje. W jednej nasz router DSL lub modem ma swój własny numer IP i przeprowadza dla nas translację adresów IP. W takim razie nie ma już potrzeby wykonywać NAT na naszym Linuksie, skoro zajmuje się tym router DSL.
Jeżeli jednak chcemy sprawować ściślejszą kontrolę nad naszą funkcją NAT, możemy dogadać się z naszym ISP (dostawcą łącza internetowego) na temat konfiguracji naszego routera DSL w "tryb mostka" (bridged mode). W tym trybie nasz firewall stanie się oficjalną częścią sieci naszego dostawcy łącza, a nasz router DSL transparentnie przekaże ruch IP pomiędzy maszynami ISP a naszym pudłem z Linuksem, bez zawiadamiania kogokolwiek o sobie. Nie będzie miał adresu IP, zamiast tego eth1 (w naszym przykładzie) ukazuje numer IP. Jeżeli ktoś spinguje nasz numer IP, otrzyma odpowiedź od naszej Linuksowej skrzynki, a nie od routera.
W przypadku takiego rodzaju ustawień, możemy zechcieć użyć SNAT (source NAT) zamiast maskarady. Poniżej linijka, którą należy dodać do naszego firewalla:
Listing 5.6: SNAT |
# iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to 1.2.3.4
|
W tym przykładzie "eth1" powinno zostać zamienione na nazwę interfejsu bezpośrednio podłączonego do naszego routera DSL, a 1.2.3.4 na nasz statyczny numer IP (naszego interfejsu sieci ethernet). Znowu, nie zapomnijmy uruchomić przekazywania IP (IP forwarding).
Szczęśliwie dla nas, NAT i maskarada działają całkiem nieźle z firewallem. Pisząc reguły zapory można po prostu zapomnieć, że używamy translacji adresów sieciowych. Nasze regułki powinny akceptować (accept), porzucać (drop) lub odrzucać (reject) pakiety w oparciu o ich "prawdziwe" źródło i adresy docelowe. Kod filtrujący zapory ogniowej widzi oryginalny adres źródłowy pakietu i ostateczny adres docelowy. To bardzo dobrze dla nas, ponieważ pozwala to pracować naszej zaporze, nawet jeżeli chwilowo wyłączymy NAT lub maskowanie pakietów.
W powyższych przykładach NAT/maskarady dodajemy reguły do łańcucha, ale również robimy coś innego. Zwróćcie uwagę na opcję "-t". Ta opcja pozwala nam wyznaczyć tabelę, do której należy nasz łańcuch. Jeżeli ją pominiemy, zostanie on zapisany do domyślnej tabeli "filter". Zatem wszystkie nasze poprzednie komendy nie związane z NAT modyfikowały łańcuch INPUT będący częścią tabeli "filter". Zawiera ona wszystkie reguły dotyczące akceptowania lub odrzucania pakietów, podczas gdy tabela "nat" (jak można przypuszczać) zawiera reguły związane z translacją adresów sieciowych. Istnieją również inne wbudowane łańcuchy tabel iptables, opisane szczegółowo na stronach manuala iptables oraz w zasobach HOWTO Rusty'ego (szukajcie odsyłaczy w Zasobach na końcu tego przewodnika).
Teraz, skoro przyjrzeliśmy się grupie możliwych ulepszeń, pora zwrócić uwagę na kolejny, bardziej elastyczny skrypt uruchamiający/wyłączający firewall:
Listing 5.7: Nasz ulepszony skrypt |
#!/bin/bash # An enhanced stateful firewall for a workstation, laptop or router that isn't # running any network services like a web server, SMTP server, ftp server, etc. # Change this to the name of the interface that provides your "uplink" # (connection to the Internet) UPLINK="eth1" # If you're a router (and thus should forward IP packets between interfaces), # you want ROUTER="yes"; otherwise, ROUTER="no" ROUTER="yes" # Change this next line to the static IP of your uplink interface for static SNAT, or # "dynamic" if you have a dynamic IP. If you don't need any NAT, set NAT to "" to # disable it. NAT="1.2.3.4" # Change this next line so it lists all your network interfaces, including lo INTERFACES="lo eth0 eth1" if [ "$1" = "start" ] then echo "Starting firewall..." iptables -P INPUT DROP iptables -A INPUT -i ! ${UPLINK} -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -p tcp -i ${UPLINK} -j REJECT --reject-with tcp-reset iptables -A INPUT -p udp -i ${UPLINK} -j REJECT --reject-with icmp-port-unreachable # Explicitly disable ECN if [ -e /proc/sys/net/ipv4/tcp_ecn ] then echo 0 > /proc/sys/net/ipv4/tcp_ecn fi # Disable spoofing on all interfaces for x in ${INTERFACES} do echo 1 > /proc/sys/net/ipv4/conf/${x}/rp_filter done if [ "$ROUTER" = "yes" ] then # We're a router of some kind, enable IP forwarding echo 1 > /proc/sys/net/ipv4/ip_forward if [ "$NAT" = "dynamic" ] then # Dynamic IP address, use masquerading echo "Enabling masquerading (dynamic ip)..." iptables -t nat -A POSTROUTING -o ${UPLINK} -j MASQUERADE elif [ "$NAT" != "" ] then # Static IP, use SNAT echo "Enabling SNAT (static ip)..." iptables -t nat -A POSTROUTING -o ${UPLINK} -j SNAT --to ${UPIP} fi fi elif [ "$1" = "stop" ] then echo "Stopping firewall..." iptables -F INPUT iptables -P INPUT ACCEPT # Turn off NAT/masquerading, if any iptables -t nat -F POSTROUTING fi |
6. Pełnostanowe serwery (stateful)
Zanim zaczniemy dopasowywać nasz skrypt do potrzeb zapory na konkretnym serwerze, należy wiedzieć, jak wyświetlić listę naszych aktualnie działających reguł firewalla. Aby zobaczyć reguły w tabeli "filter" łańcucha INPUT, należy wpisać:
Listing 6.1: Przegląd reguł. |
# iptables -v -L INPUT
|
Opcja -v pozwala na szczegółowy ogląd, dzięki czemu można zobaczyć wszystkie pakiety i bajty przepuszczane przez regułę. Można również sprawdzić nasz POSTROUTING w tabeli "nat" przy pomocy komendy:
Listing 6.2: Oglądanie reguł tabeli POSTROUTING |
# iptables -t nat -v -L POSTROUTING
Chain POSTROUTING (policy ACCEPT 399 packets, 48418 bytes)
pkts bytes target prot opt in out source destination
2728 170K SNAT all -- any eth1 anywhere anywhere
to:215.218.215.2
|
W tej chwili nasz firewall nie akceptuje żadnych połączeń z zewnątrz do usług na naszej maszynie, ponieważ przepuszcza wyłącznie przychodzące pakiety z połączeń nawiązanych (ESTABLISHED) lub powiązanych (RELATED). Ponieważ upuszcza wszystkie pakiety przychodzące w stanie NEW, każda próba nawiązania połączenia zostaje odrzucona bezwarunkowo. Tymczasem poprzez wybiórcze pozwalanie niektórym typom ruchu przychodzącego na przekroczenie naszej zapory, możemy udostępnić niektóre z usług dla osób z zewnątrz.
Chociaż chcemy dopuścić niektóre połączenia przychodzące, prawdopodobnie nie dotyczy to każdego ich rodzaju. Sensownie jest zacząć od polityki "zabroń domyślnie" (tak jak mamy to teraz) i otwierać dostęp do tych usług, które mamy zamiar udostępnić. Na przykład, jeżeli uruchamiamy serwer WWW, dopuścimy pakiety w stanie NEW do naszej maszyny, ale tylko skierowane na port 80 (HTTP). I to wszystko, co potrzebujemy ustawić. Skoro tylko wpuścimy pakiety w stanie NEW, pozwoliliśmy na nawiązanie połączenia. Skoro połączenie zostało nawiązane, włączyła się nasza działająca reguła wpuszczająca pakiety stanu RELATED i ESTABLISHED i pozwala połączeniu HTTP działać bez przeszkód.
Przyjrzyjmy się "sercu" naszej zapory i nowej regule dopuszczającej przychodzące połączenia HTTP:
Listing 6.3: Przykład pełnostanowego HTTP. |
iptables -P INPUT DROP
iptables -A INPUT -i ! ${UPLINK} -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Nowa reguła
iptables -A INPUT -p tcp --dport http -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp -i ${UPLINK} -j REJECT --reject-with tcp-reset
iptables -A INPUT -p udp -i ${UPLINK} -j REJECT --reject-with
icmp-port-unreachable
|
Nowa reguła pozwala przejść przez zaporę przychodzącym pakietom TCP w stanie NEW skierowanym na port 80. Zwróćmy uwagę na położenie tej reguły. To bardzo istotne, że pojawia się ona przed regułami REJECT. Skoro iptables uruchomi pierwszą pasującą regułę, umieszczenie jej po linijce REJECT spowodowałoby, że nie przyniosłaby żadnego efektu.
Teraz przyjrzyjmy się naszemu finalnemu skryptowi zapory, który może zostać zastosowany na laptopie, stacji roboczej, routerze czy serwerze (lub jakiejkolwiek ich kombinacji).
Listing 6.4: Nasz finalny skrypt firewalla |
#!/bin/bash # Our complete stateful firewall script. This firewall can be customized for # a laptop, workstation, router or even a server. :) # Change this to the name of the interface that provides your "uplink" # (connection to the Internet) UPLINK="eth1" # If you're a router (and thus should forward IP packets between interfaces), # you want ROUTER="yes"; otherwise, ROUTER="no" ROUTER="yes" # Change this next line to the static IP of your uplink interface for static SNAT, or # "dynamic" if you have a dynamic IP. If you don't need any NAT, set NAT to "" to # disable it. NAT="1.2.3.4" # Change this next line so it lists all your network interfaces, including lo INTERFACES="lo eth0 eth1" # Change this line so that it lists the assigned numbers or symbolic names (from # /etc/services) of all the services that you'd like to provide to the general # public. If you don't want any services enabled, set it to "" SERVICES="http ftp smtp ssh rsync" if [ "$1" = "start" ] then echo "Starting firewall..." iptables -P INPUT DROP iptables -A INPUT -i ! ${UPLINK} -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # Enable public access to certain services for x in ${SERVICES} do iptables -A INPUT -p tcp --dport ${x} -m state --state NEW -j ACCEPT done iptables -A INPUT -p tcp -i ${UPLINK} -j REJECT --reject-with tcp-reset iptables -A INPUT -p udp -i ${UPLINK} -j REJECT --reject-with icmp-port-unreachable # Explicitly disable ECN if [ -e /proc/sys/net/ipv4/tcp_ecn ] then echo 0 > /proc/sys/net/ipv4/tcp_ecn fi # Disable spoofing on all interfaces for x in ${INTERFACES} do echo 1 > /proc/sys/net/ipv4/conf/${x}/rp_filter done if [ "$ROUTER" = "yes" ] then # We're a router of some kind, enable IP forwarding echo 1 > /proc/sys/net/ipv4/ip_forward if [ "$NAT" = "dynamic" ] then # Dynamic IP address, use masquerading echo "Enabling masquerading (dynamic ip)..." iptables -t nat -A POSTROUTING -o ${UPLINK} -j MASQUERADE elif [ "$NAT" != "" ] then # Static IP, use SNAT echo "Enabling SNAT (static ip)..." iptables -t nat -A POSTROUTING -o ${UPLINK} -j SNAT --to ${UPIP} fi fi elif [ "$1" = "stop" ] then echo "Stopping firewall..." iptables -F INPUT iptables -P INPUT ACCEPT # Turn off NAT/masquerading, if any iptables -t nat -F POSTROUTING fi |
7. Budowanie lepszego serwera zapory ogniowej
Często można uczynić zaporę odrobinkę "lepszą". Oczywiście, "lepszą" zależnie od indywidualnych potrzeb. Nasz działający skrypt może im w pełni odpowiadać, lub wymagać dodatkowego "podrasowania". Ta sekcja to trochę "książka kucharska" pełna pomysłów pokazujących sposoby ulepszania naszego firewalla pełnostanowego.
Na razie nie omawialiśmy w ogóle kwestii zapisywania do logów. Mamy specjalny obiekt o nazwie LOG, którego można do tego użyć. Do niego należy specjalna opcja --log-prefix, która pozwala określić brzmienie tekstu, który pojawi się obok obrazu pakietu w logach systemowych. Poniżej zamieszczam przykład regułki logowania:
Listing 7.1: Przykład reguły logowania. |
# iptables -A INPUT -j LOG --log-prefix "bad input:"
|
Nie chcielibyśmy jej dodać jako pierwszej regułki łańcucha INPUT, ponieważ spowodowałoby to zapisywanie do logów każdego pakietu, który byśmy otrzymali! Zamiast tego należy ją umieścić na końcu łańcucha INPUT z intencją logowania dziwnych pakietów i innych anomalii.
Istotna uwaga na temat obiektu LOG. Normalnie, kiedy pakiet pasuje do reguły, może zostać zaakceptowany, odrzucony lub upuszczony, a żadne następne reguły nie zostaną uruchomione. Jeżeli będzie pasował do reguły logowania, zostanie to zapisane do logów, chociaż nie został zaakceptowany, odrzucony ani upuszczony. Zamiast tego pakiet podąża do następnej reguły lub zostaje zastosowana domyślna polityka, o ile reguła logowania jest ostatnią w łańcuchu.
Obiekt LOG może również być połączony z modułem "limit" (opisanym na stronie man iptables), aby zminimalizować duplikowanie wpisów do logów. Poniżej przykład:
Listing 7.2: Limitowanie wpisów do logów. |
# iptables -A INPUT -m state --state INVALID -m limit --limit 5/minute -j LOG --log-prefix "INVALID STATE:"
|
iptables pozwala definiować własne łańcuchy, które mogą zostać zaliczone do obiektów w regułach. Ci, którzy chcą opanować tę sztukę powinni spędzić trochę czasu przekopując się przez Packet filtering HOWTO na stronie projektu netfilter/iptables (http://www.netfilter.org/).
Wprowadzanie w życie polityki korzystania z sieci
Zapory ogniowe dają dużo władzy tym, którzy chcą wdrożyć politykę korzystania z sieci korporacyjnej czy akademickiej. Można kontrolować, które pakiety nasza maszyna przekaże dalej poprzez dodawanie reguł i ustawianie polityki dla łańcucha FORWARD. Dzięki dodaniu reguły do łańcucha OUTPUT można kontrolować, co się stanie z pakietami, które zostały stworzone lokalnie, przez użytkowników naszej maszyny linuksowej. iptables posiada również niesamowite możliwości filtrowania lokalnie wygenerowanych pakietów w oparciu o ich użytkownika (uid lub gid). Aby znaleźć więcej informacji na ten temat, przeszukaj strony man iptables ze słowem "owner".
Inne poglądy na bezpieczeństwo
Nasza przykładowa zapora ogniowa monitoruje uważnie wyłącznie ruch przychodzący z Internetu, jako że założyliśmy, że cały ruch pochodzący z naszego LAN-u jest w pełni zaufany. Zależnie od sieci, takie założenie może, ale nie musi być w porządku. Nic nie stoi na przeszkodzie, abyśmy skonfigurowali nasz firewall do ochrony przed ruchem pochodzącym z wnętrza LAN. Należy również rozważyć, czy różne rejony sieci wymagają takiej samej ochrony. Mogłoby mieć sens skonfigurowanie dwóch oddzielnych "stref" ochrony LAN, każdej z jej własną polityką bezpieczeństwa.
W tej sekcji wymienię kilka pomysłów, które można by uznać za pomocne przy konstruowaniu naszego własnego pełnostanowego firewalla. Zacznijmy od ważnego narzędzia...
tcpdump stanowi podstawowe narzędzie do niskopoziomowego podglądu wymiany pakietów i weryfikowania czy nasza zapora pracuje prawidłowo. Jeżeli tego nie macie, musicie to zainstalować. Jeżeli już to macie, zacznijcie używać.
Zajrzyjcie na stronę projektu netfilter/iptables (http://www.netfilter.org). Znajdziecie tam wiele różnych rzeczy, w tym źródła iptables i netfilter FAQ. Także Rusty's Remarkably Guides stanowią doskonałe HOWTO, zawierające podstawowe koncepcje pracy w sieci, netfilter (iptables) HOWTO, NAT HOWTO, netfilter hacking HOWTO dla programistów i deweloperów.
W sieci dostępna jest duża ilość różnych materiałów na temat filtrowania pakietów, ale nie należy zapominać o tych podstawowych. Strona man iptables jest bardzo szczegółowym i błyskotliwym przykładem na to, jak powinna wyglądać strona man. I świetnie się ją czyta.
Advanced Linux routing and traffic control HOWTO
Warto zajrzeć też do Advanced Linux Routing and Traffic Control HOWTO. Można znaleźć tam rozdział o tym, jak użyć iptables do znakowania pakietów, a następnie funkcjonalności routera Linux do trasowania pakietów na podstawie tych znaczników.
Uwaga: To HOWTO zawiera odwołania do funkcjonalności Linuksa zwanej kontrola ruchu (traffic control) czyli quality od service (dostępnej przez nową komendę tc). Ta nowa funkcja, pomimo że bardzo interesująca, na razie nie jest dobrze udokumentowana. Próba ustalenia wszystkich aspektów kontroli ruchu w Linuksie może się stać dosyć frustrującym zadaniem. |
Użytkownicy z pytaniami na temat użytkowania netfilter/iptables, jego ustawień i konfiguracji mogą uzyskać pomost na liście mailingowej użytkowników netfilter. Lista jest także dla tych, którzy chcieliby podzielić się swoim doświadczeniem i wiedzą.
Pytanie, sugestie lub pomoc przy chęć pomocy w rozwijaniu netfilter/iptables, można kierować na listę mailingową użytkowników netfilter.
Możecie również przeglądać archiwa tych list, znajdują się one pod tymi samymi adresami.
Building Internet Firewalls, Second Edition - Budowanie internetowych zapór ogniowych - edycja druga
W lipcu 2000 roku wydawnictwo O'Reilly opublikowało znakomitą książkę -- Building Internet Firewalls, Second Edition. Stanowi ona doskonały podręcznik, szczególnie w momencie, kiedy chcemy skonfigurować nasz firewall, aby akceptował (lub całkowicie odrzucał) mało znany protokół, z którym nie jesteśmy "na ty".
Cóż, to by było na tyle, jeśli chodzi o listę pomocy. Kończy się również nasz przewodnik. Mam nadzieję, że będzie on użyteczny. Proszę o wszelkie opinie na jego temat.
Wszystkie uwagi na temat tego przewodnika należy kierować do autora, Daniela Robbinsa na adres Daniel Robbins.