Single Packet Authorization, Port Knocking i inne zabezpieczenia serwera
Kategoria: Artykuły, etykiety: ssh, spa, single packet authorization, port knocking, pk, knockd, iptables, fwknop, brute force
Dodany: 2014-07-24 14:23
(zmodyfikowany: 2014-07-24 14:24)
Przez: morfik
Wyświetleń: 12457
Poniższy artykuł ma na celu pokazać jak w prosty sposób w oparciu o wielopoziomowe zabezpieczenia dozbroić nieco serwer, tak by znajdujące się na nim usługi były należycie chronione. Zostanie to pokazane na przykładzie ssh, bo chyba każdy serwer posiada zdalny dostęp przez shella i za bardzo nie godzi się by zostawić tę usługę otwartą na zewnętrzny świat wirtualny bez jakiegokolwiek nadzoru.
Podstawy
Zanim jednak przejdziemy do bardziej zaawansowanych rzeczy, przydałoby się trochę podstaw. Chyba każdy już zdołał wyczytać na necie, że pierwsze co trzeba zrobić w przypadku usług wrażliwych, to zdjęcie ich z domyślnego portu i przypisanie ich na jakiś port 5-cyfrowy. Zwykle każda aplikacja posiada swoje pliki konfiguracyjne i można w nich sprecyzować parametry dla daemona, który startuje wraz z systemem. W tym przypadku będzie to plik /etc/ssh/sshd_config , ustawiamy w nim odpowiedni port:
Port 12345
Druga sprawa to pliki /etc/hosts.allow oraz /etc/hosts.deny , w których definiujemy hosty/sieci mogące komunikować się z określonymi usługami. Te dwa pliki są przeszukiwane jeden po drugi w celu odnalezienia dopasowań. Pierw jest sprawdzany /etc/hosts.allow i jeśli tam nie zostanie znaleziona para usługa:host, zacznie się przeszukiwanie pliku /etc/hosts.deny . Domyślne ustawienia zezwalają wszystkim hostom na łączenie się do wszystkich usług, dlatego też w przypadku /etc/hosts.deny dobrze jest wpisać tę poniższą linijkę:
ALL:ALL EXCEPT localhost:DENNY
Zabroni ona wszystkim hostom uzyskać dostęp do jakichkolwiek usług znajdujących się na serwerze. Oczywiście localhost zostanie z tej reguły wyłączony. Z kolei w pliku /etc/hosts.allow trzeba zdefiniować maszyny (adresy ip), którym dostęp zostanie przyznany do określonych usług, w tym przypadku, do ssh, przykładowo:
sshd: 192.168.1.0/255.255.255.0
Więcej info na temat formatu pliku, można znaleźć w man HOSTS_ACCESS(5).
Filtr iptables
Kolejnym zabezpieczeniem jest oczywiście filter iptables. By filter wyglądał w miarę przyzwoicie, trzeba go dobrze zaprojektować -- tak by pakiety przemierzały jak najkrótszą drogę. W naszym przypadku musimy zatroszczyć się o nowe połączenia do daemona ssh (protokół tcp) na porcie, który został określony w pliku konfiguracyjnym, czyli będzie to 12345. Jako, że trochę dobezpieczymy tę usługę, stworzymy dla niej osobny łańcuch ssh , w którym to kolejne reguły dotyczące już samego ssh będą definiowane. Całość filtra będzie mieć postać skryptu, by łatwiej się nam pracowało. Poniżej znajduje się kod wyjściowy, od którego zaczniemy:
#!/bin/bash
iptables -t filter -F
iptables -t filter -X
iptables -t filter -N tcp
iptables -t filter -N udp
iptables -t filter -N ssh
iptables -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -t filter -A INPUT -i lo -j ACCEPT
iptables -t filter -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -t filter -A INPUT -p udp -m conntrack --ctstate NEW -j udp
iptables -t filter -A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j tcp
iptables -t filter -A INPUT -p tcp -m recent --set --name tcp-portscan -j REJECT --reject-with tcp-reset
iptables -t filter -A INPUT -p udp -m recent --set --name udp-portscan -j REJECT --reject-with icmp-port-unreachable
iptables -t filter -A INPUT -j REJECT --reject-with icmp-proto-unreachable
iptables -t filter -A tcp -p tcp -m recent --update --seconds 300 --name tcp-portscan -j REJECT --reject-with tcp-reset
iptables -t filter -A udp -p udp -m recent --update --seconds 300 --name udp-portscan -j REJECT --reject-with icmp-port-unreachable
iptables -t filter -A tcp -p tcp --dport 12345 -j ssh -m comment --comment "ssh"
Od tej pory wszystkie pakiety mające protokół TCP i rozpoczynające nowe połączenia będą kierowane do łańcucha tcp. Z tego łańcucha pakiety adresowane na port 12345 będą przechodzić do łańcucha ssh. Wszystkie pozostałe pakiety dla połączeń już ustanowionych będą łapane przez regułę --ctstate RELATED,ESTABLISHED . Dla czytelności reguł iptables można korzystać z modułu comment , który ułatwi nieco orientację przy podglądzie filtra.
Przede wszystkim kluczową rolę w zaporze pełnią stany połączeń, te z kolei mogą przybrać formę NEW, RELATED, ESTABLISHED. Wszystko co nie pasuje do tych stanów, jest traktowane jako INVALID -- trafiają tam pakiety, których flagi mają niemożliwe kombinacje z punktu widzenia poprawnej komunikacji sieciowej, np. gdy pakiet ma ustawione flagi syn i fin jednocześnie. Jednak istnieje szereg pakietów, które mogą potencjalnie zagrażać bezpieczeństwu maszyny i nie są one uwzględnione w stanie INVALID i takie właśnie pakiety są używane do skanowania portów w celu wykrycia usług znajdujących się na serwerze. Krótko mówiąc, stan INVALID nie złapie skanów UDP, ACK oraz SYN, (w nmap -sU, -sA, -sS).
Nasuwa się pytanie, w jaki sposób zatem się bronić przed tego typu skanami portów? Po to są właśnie te 4 regułki z -j REJECT . Tworzą one dwie listy (po jednej dla protokołu UDP oraz TCP) z adresami ip, z których nadeszło zapytanie na zamknięte porty. Przez następne 300s (5min), serwer będzie zwracał komunikat tcp-reset lub icmp-port-unreachable (w zależności od protokołu) hostom, które są na liście i nie będzie miało przy tym znaczenia czy port, do którego trafił pakiet jest otwarty czy zamknięty. Dodatkowo, każdy nowy pakiet od takiego delikwenta będzie skutkował odświeżeniem czasu, przez jaki serwer będzie odpowiadał powyższymi komunikatami, co czyni skanowanie portów wielce niepraktycznym -- czas skanowania jednego portu to minimum 5min, a portów jest 65536. Trzeba jednak pamiętać, że nie da rady się obronić przed skanem otwartego portu i dlatego właśnie przenieśliśmy usługę ssh z domyślnego portu 22 na port 12345.
Ataki brute force
Pora ogarnąć ruch na porcie 12345. Zwykle ludzie zostawiają otwarty port i nasłuchującą na nim usługę samym sobie. A co się stanie gdy ktoś odnajdzie naszego shella na porcie 12345? W takim przypadku nie mamy żadnej obrony i nasz serwer stoi otworem przed atakującym, a my znajdujemy się mniej więcej w takiej samej sytuacji co w przypadku gdybyśmy zostawili ssh na domyślnym porcie 22. Oczywiście, osoba próbująca dostać się do serwera niekoniecznie musi być kimś kogo w ogóle nie znamy. Może się zdarzyć, że to nasz były dobry znajomy, który chce się odgryźć za coś cośmy mu uczynili kiedyś tam w przeszłości. Przydałoby się zatem jakoś zablokować ataki brute force pod kątem złamania hasła do jakiegoś shellowego konta. By zaimplementować tego typu rozwiązanie, dodajemy kilka dodatkowych linijek do naszego filtra:
#!/bin/bash
iptables -t filter -F
iptables -t filter -X
iptables -t filter -N tcp
iptables -t filter -N udp
iptables -t filter -N ssh
iptables -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -t filter -A INPUT -i lo -j ACCEPT
iptables -t filter -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -t filter -A INPUT -p udp -m conntrack --ctstate NEW -j udp
iptables -t filter -A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j tcp
iptables -t filter -A INPUT -p tcp -m recent --set --name tcp-portscan -j REJECT --reject-with tcp-reset
iptables -t filter -A INPUT -p udp -m recent --set --name udp-portscan -j REJECT --reject-with icmp-port-unreachable
iptables -t filter -A INPUT -j REJECT --reject-with icmp-proto-unreachable
iptables -t filter -A tcp -p tcp -m recent --update --seconds 300 --name tcp-portscan -j REJECT --reject-with tcp-reset
iptables -t filter -A udp -p udp -m recent --update --seconds 300 --name udp-portscan -j REJECT --reject-with icmp-port-unreachable
iptables -t filter -A tcp -p tcp --dport 12345 -j ssh -m comment --comment "ssh"
iptables -t filter -A ssh -m recent --name ssh-bruteforce --rttl --rcheck --hitcount 3 --seconds 20 -j DROP
iptables -t filter -A ssh -m recent --name ssh-bruteforce --rttl --rcheck --hitcount 4 --seconds 1800 -j DROP
iptables -t filter -A ssh -m recent --name ssh-bruteforce --set -j ACCEPT
Krótko pisząc, po 3 pakiecie inicjującym połączenie (proces logowania), dany host dostanie bana na 20s. Jeśli w czasie tych 20s spróbuje on kolejny raz się podłączyć -- dostanie bana na 30min. Listy z adresami znajdują się w katalogu /proc/net/xt_recent/ . Trzeba wziąć jednak pod uwagę, że powyższe ustawienia mogą umożliwić atak DOS -- w przypadku gdyby ktoś zespoofował adres IP, może dojść do zbanowania klienta, który ma prawo się podłączyć.
Port knocking
Kolejną opcją w wachlarzu zabezpieczeń jaką mamy do dyspozycji jest port knocking . Generalnie chodzi o zewnętrzne otwieranie portów w oparciu o stukanie w inne (zamknięte) porty. Czyli mamy ssh na porcie 12345 ale ten port zostanie odfiltrowany w iptables i nikt z zewnątrz nie da rady się podłączyć.
By wykorzystać port knocking i zachować przy tym możliwość obrony przed atakami brute force, musimy przepisać reguły do poniższej postaci:
#!/bin/bash
iptables -t filter -F
iptables -t filter -X
iptables -t filter -N tcp
iptables -t filter -N udp
iptables -t filter -N ssh
iptables -t filter -N approved
iptables -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -t filter -A INPUT -i lo -j ACCEPT
iptables -t filter -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -t filter -A INPUT -p udp -m conntrack --ctstate NEW -j udp
iptables -t filter -A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j tcp
iptables -t filter -A INPUT -p tcp -m recent --set --name tcp-portscan -j REJECT --reject-with tcp-reset
iptables -t filter -A INPUT -p udp -m recent --set --name udp-portscan -j REJECT --reject-with icmp-port-unreachable
iptables -t filter -A INPUT -j REJECT --reject-with icmp-proto-unreachable
iptables -t filter -A tcp -p tcp -m multiport --dports 2222,3333,4444 --tcp-flags ALL SYN -m conntrack --ctstate NEW -j REJECT --reject-with tcp-reset
iptables -t filter -A tcp -p tcp -m recent --update --seconds 300 --name tcp-portscan -j REJECT --reject-with tcp-reset
iptables -t filter -A udp -p udp -m recent --update --seconds 300 --name udp-portscan -j REJECT --reject-with icmp-port-unreachable
iptables -t filter -A tcp -p tcp --dport 12345 -j ssh -m comment --comment "ssh"
iptables -t filter -A ssh -m recent --name ssh-bruteforce --rttl --rcheck --hitcount 3 --seconds 20 -j DROP
iptables -t filter -A ssh -m recent --name ssh-bruteforce --rttl --rcheck --hitcount 4 --seconds 1800 -j DROP
iptables -t filter -A ssh -m recent --name ssh-bruteforce --set -j approved
To co uległo zmianie, to przekierowanie ruchu do kolejnego łańcucha, zamiast wcześniejszego zaakceptowania pakietów (ostatnia reguła). W tym nowym łańcuchu będą dodawane hosty, które zainicjowały poprawną sekwencję dla port knocking. Dodatkowo, wszystkie porty użyte do port knockingu trzeba wyjąć spod ochrony przed port skanami, w przeciwnym razie, sami uderzymy w zaporę, a że wyślemy 3 pakiety, to z miejsca dostaniemy bana na 30min.
Potrzebujemy jeszcze doinstalować pakiet knockd . Musimy także wyedytować dwa pliki: /etc/default/knockd oraz /etc/knockd.conf . W pierwszym pliku aktywujemy możliwość uruchamiania się daemona. W drugim zaś definiujemy regułki.
W zależności od tego co chcemy osiągnąć, mamy, z grubsza, do dyspozycji 2 opcje. Pierwsza z nich polega na manualnym otwieraniu i zamykaniu portów, czyli otwieramy ręcznie porty, robimy swoje i za godzinę zamykamy porty. Druga opcja jest podobna do pierwszej z tym, że furtka zostanie otwarta tylko przez pewien okres czasu -- w takiej sytuacji po wprowadzeniu loginu i hasła, pakiety ze stanem NEW zostaną zaakceptowane przez serwer, a po paru sekundach port się zamknie automatycznie. Podłączony host i tak nie potrzebuje już by ten port był otwarty, bo dalsza komunikacja będzie się odbywać za pomocą regułki --ctstate RELATED,ESTABLISHED. Dzięki takiemu rozwiązaniu, port niby jest otwarty ale jednak zamknięty.
Edytujemy teraz plik /etc/knockd.conf i w przypadku opcji pierwszej, config knockd będzie wyglądał jak poniżej:
[options]
UseSyslog
[OpenSSH]
sequence = 2222:tcp,3333:tcp,4444:tcp
seq_timeout = 5
command = /sbin/iptables -A approved -s %IP% -j ACCEPT
tcpflags = syn
[CloseSSH]
sequence = 4444:tcp,3333:tcp,2222:tcp
seq_timeout = 5
command = /sbin/iptables -D approved -s %IP% -j ACCEPT
tcpflags = syn
Widnieją tam dwie sekwencje, z których jedna otworzy port 12345, a druga go zamknie -- przez dopisanie/usunięcie konkretnego adresu IP.
Jeśli chodzi zaś o automatyczne zamykanie furtki, można posłużyć się poniższą konfiguracją:
[options]
UseSyslog
[OpenCloseSSH]
sequence = 2222:tcp,3333:tcp,4444:tcp
seq_timeout = 5
tcpflags = syn
start_command = /sbin/iptables -A approved -s %IP% -j ACCEPT
cmd_timeout = 15
stop_command = /sbin/iptables -D approved -s %IP% -j ACCEPT
seq_timeout odpowiada za czas pukania w porty, z kolei cmd_timeout jest to czas przez jaki port zostanie otwarty.
By teraz otworzyć zdalnie porty, trzeba posłużyć się poleceniem knock na maszynie klienckiej, przykładowo:
# knock -v 192.168.1.150 2222:tcp 3333:tcp 4444:tcp
hitting tcp 192.168.1.150:2222
hitting tcp 192.168.1.150:3333
hitting tcp 192.168.1.150:4444
Ja korzystam z opcji automatycznego zamykania portu i w logu (na serwerze) po wydaniu powyższego polecenia dostaję takie komunikaty:
# /usr/sbin/knockd -i bond0 -v -D --config /etc/knockd.conf
config: new section: 'options'
config: usesyslog
config: new section: 'OpenCloseSSH'
config: OpenCloseSSH: sequence: 2222:tcp,3333:tcp,4444:tcp
config: OpenCloseSSH: seq_timeout: 5
config: tcp flag: SYN
config: OpenCloseSSH: start_command: /sbin/iptables -A approved -s %IP% -j ACCEPT
config: OpenCloseSSH: cmd_timeout: 15
config: OpenCloseSSH: stop_command: /sbin/iptables -D approved -s %IP% -j ACCEPT
ethernet interface detected
Local IP: 192.168.1.150
listening on bond0...
2014-06-19 17:24:03: tcp: 192.168.1.20:45487 -> 192.168.1.150:2222 74 bytes
192.168.1.20: OpenCloseSSH: Stage 1
2014-06-19 17:24:03: tcp: 192.168.1.20:46354 -> 192.168.1.150:3333 74 bytes
192.168.1.20: OpenCloseSSH: Stage 2
2014-06-19 17:24:03: tcp: 192.168.1.20:33894 -> 192.168.1.150:4444 74 bytes
192.168.1.20: OpenCloseSSH: Stage 3
192.168.1.20: OpenCloseSSH: OPEN SESAME
OpenCloseSSH: running command: /sbin/iptables -A approved -s 192.168.1.20 -j ACCEPT
192.168.1.20: OpenCloseSSH: command timeout
OpenCloseSSH: running command: /sbin/iptables -D approved -s 192.168.1.20 -j ACCEPT
Jak widać powyżej, do łańcucha approved został dodany adres IP maszyny, która zainicjowała poprawną sekwencję dla port knocking. Po chwili także widać wykonanie się polecenia zamykającego port. Oczywiście polecenie może być dowolne, niekoniecznie musi zawierać jedną regułkę iptables, można np. sprecyzować skrypt.
Korzystanie z port knocking ma jednak jedną ale za to ogromną wadę -- można podsłuchać sekwencję portów. A jeśli wróg będzie znał ją, jego IP zostanie dopisane jako zaufane. Na szczęście serwer jeszcze chronią reguły od brute force oraz wpisy w /etc/hosts.allow i /etc/hosts.deny . W przypadku braku tych plików, atakujący mógłby się dostać do serwera, a tak nawet w przypadku znania hasła, dostęp mu nie zostanie przyznany -- atakujący zobaczy jedynie poniższy komunikat:
$ ssh morfik@192.168.1.150 -p 12345
ssh: Exited: Error connecting: Connection refused
Jeśli atakujący nie ma pojęcia o zastosowanej metodzie, ten mechanizm powinien być w miarę bezpieczny. Ja jednak zawsze zakładam, że niezbyt przychylne mi osoby wiedzą o moich zabezpieczeniach więcej ode mnie. xD I pewnie nie tylko ja tak uważam, bo ktoś opracował projekt zwany fwknop .
Single Packet Authorization
fwknop wykorzystuje jeden pakiet (Single Packet Authorization) do otwierania portu/portów i ma za zadanie wyeliminowanie wad, które ma powyżej opisany przeze mnie knockd. Proces uwierzytelniania i autoryzacji odbywa się przy pomocy czegoś na wzór zaszyfrowanego ciasteczka. Dane są generowane per klient i dostarczane na serwer. Później już tylko się przesyła ciasteczko do serwera i po jego pomyślnym odszyfrowaniu, serwer podejmuje szereg działań -- od dodania prostej regułki do iptables, na wykonywaniu skryptów kończąc.
Mając do dyspozycji dwie maszyny -- serwer oraz klient -- na pierwszej z nich instalujemy fwknop-server , a na drugiej fwknop-client .
Najpierw zajmijmy się konfiguracją klientów -- musimy wygenerować klucze szyfrujące. Wpisujemy w terminalu poniższą linijkę:
$ fwknop -A tcp/12345 -a 192.168.1.20 -D 192.168.1.150 --key-gen --use-hmac -p 23451 --save-rc-stanza
[*] Creating initial rc file: /home/morfik/.fwknoprc.
[+] Wrote Rijndael and HMAC keys to rc file: /home/morfik/.fwknoprc
>Parametr --save-rc-stanza zapisuje wprowadzone w linijce opcje w pliku .fwknoprc i jak można zauważyć, taki plik został stworzony w /home/morfik/.fwknoprc -- zajrzymy do środka:
[192.168.1.150]
SPA_SERVER_PORT 23451
ALLOW_IP 192.168.1.20
ACCESS tcp/12345
SPA_SERVER 192.168.1.150
KEY_BASE64 QwJANH9Aic0KVwanEgWuS2uF+M+9uwmhJKwV5/m2WNk=
HMAC_KEY_BASE64 8o3xm80SIt8jd6YjgPT1V4gOvE+r9y2xNOl9ktSk8Hbt//OCNn0j1o4WKbxVbB/8R+c4i+7ketBqx38TaErfaw==
USE_HMAC Y
KEY_BASE64 oraz HMAC_KEY_BASE64 to klucze, które musimy przesłać w bezpieczny sposób na serwer. Opcji do precyzowania jest dość sporo, powyższy kawałek to niezbędne minimum.
W przypadku serwera, musimy oczywiście włączyć daemona w pliku /etc/default/fwknop-server . Mamy także dodatkowo dwa inne pliki, na które musimy rzucić okiem -- /etc/fwknop/access.conf oraz /etc/fwknop/fwknopd.conf . W pliku /etc/fwknop/access.conf musimy umieścić klucze wszystkich hostów, które będą mieć prawo łączyć się do serwera, przykładowo:
SOURCE 192.168.1.20
OPEN_PORTS tcp/12345
FW_ACCESS_TIMEOUT 20
REQUIRE_SOURCE_ADDRESS Y
KEY_BASE64 QwJANH9Aic0KVwanEgWuS2uF+M+9uwmhJKwV5/m2WNk=
HMAC_KEY_BASE64 8o3xm80SIt8jd6YjgPT1V4gOvE+r9y2xNOl9ktSk8Hbt//OCNn0j1o4WKbxVbB/8R+c4i+7ketBqx38TaErfaw==
Tego typu zwrotek można umieszczać ile się chce, trzeba jednak pamiętać, że plik jest przeszukiwany od góry do dołu pod kątem dopasowań i pierwsze z nich zostanie zaakceptowane. Porty oraz adresy można podawać w jednej linijce, oddzielając je za pomocą przecinka, z kolei FW_ACCESS_TIMEOUT określa czas otwarcia okna.
W drugim pliku, /etc/fwknop/fwknopd.conf , definiujemy opcje samego daemona:
VERBOSE 1;
PCAP_FILTER udp port 23451;
PCAP_INTF bond0;
IPT_INPUT_ACCESS ACCEPT, filter, tcp, 2, fwknop_input, 1;
Ostatnia linijka z tych powyżej określa, w którym miejscu umieścić łańcuch fwknop_input, do którego z kolei trafiać będą reguły generowane przez fwknopd -- ACCEPT określa politykę reguły, filter tablicę iptables, tcp łańcuch w tej tablicy. Cyfra 2, definiuje pozycję, na której umieścić łańcuch fwknop_input, z kolei zaś cyfra 1 precyzuje, na której pozycji będą dodawane reguły przez fwknopd.
Jeśli mamy niestandardową konfigurację interfejsów sieciowych, trzeba sprecyzować, który z nich ma być poddany analizie. W moim przypadku jest to bond0. Jeśli interfejs będzie błędy, np. nie będzie miał adresu IP, daemon nie wystartuje. Dobrze jest też zmienić domyślny port, z którego wyłapywane będą pakiety klienta fwknop. Warto także ustawić tryb verbose i obserwować logi w syslogu.
W przypadku daemona fwknopd można zrezygnować z pewnych rzeczy, które ustawiliśmy wcześniej w filtrze, np. można pozbyć się mechanizmu przeciwko brute force -- jakby nie patrzeć, w tej chwili dysponujemy ciasteczkami i jeśli admini nabroili coś, możemy zwyczajnie skasować ich regułkę z pliku konfiguracyjnego, a bez niej nigdy nie dostaną się do ssh i wszystkich innych usług chronionych przez SPA. Dodatkowo, nie trzeba wyłączać portów spod mechanizmu antyskanowego, tak jak to miało miejsce w przypadku knockd. Nie musimy też definiować osobnego łańcucha dla połączeń ssh. Zatem usuwamy ze skryptu iptables zbędne rzeczy, w tej chwili powinien on wyglądać jak poniżej:
#!/bin/bash
iptables -t filter -F
iptables -t filter -X
iptables -t filter -N tcp
iptables -t filter -N udp
iptables -t filter -N fwknop_input
iptables -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -t filter -A INPUT -i lo -j ACCEPT
iptables -t filter -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -t filter -A INPUT -p udp -m conntrack --ctstate NEW -j udp
iptables -t filter -A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j tcp
iptables -t filter -A INPUT -p tcp -m recent --set --name tcp-portscan -j REJECT --reject-with tcp-reset
iptables -t filter -A INPUT -p udp -m recent --set --name udp-portscan -j REJECT --reject-with icmp-port-unreachable
iptables -t filter -A INPUT -j REJECT --reject-with icmp-proto-unreachable
iptables -t filter -A tcp -p tcp -m recent --update --seconds 300 --name tcp-portscan -j REJECT --reject-with tcp-reset
iptables -t filter -A tcp -j fwknop_input
iptables -t filter -A udp -p udp -m recent --update --seconds 300 --name udp-portscan -j REJECT --reject-with icmp-port-unreachable
Jeśli ustawiliśmy wszystko z powyższym opisem, po przesłaniu ciasteczka, będziemy mieć okno otwarte przez 20s -- na ten czas zostanie dopisana reguła do iptables. Po zamknięciu okna, komunikacja będzie się odbywać dokładnie na takiej samej zasadzie co w przypadku knockd, czyli za pośrednictwem reguły --ctstate RELATED,ESTABLISHED .
Odpalmy zatem na serwerze daemona i zobaczmy co zostanie wypisane w logu:
Jul 24 12:57:22 morfikownia fwknopd[58372]: [+] Writing my PID (58372) to the lock file: /var/run/fwknop/fwknopd.pid
Jul 24 12:57:22 morfikownia fwknopd[58372]: Starting fwknopd
Jul 24 12:57:22 morfikownia fwknopd[58372]: Using Digest Cache: '/var/run/fwknop/digest.cache' (entry count = 5)
Jul 24 12:57:22 morfikownia fwknopd[58372]: rule_exists_chk_support() CMD: '/sbin/iptables -C tcp -t filter -j fwknop_input 2>&1' (res: 0, err: iptables v1.4.21: Couldn't load target `fwknop_input':No such file or directory#012#012Try `iptables -h' or 'iptables --help' for more information.)
Jul 24 12:57:22 morfikownia fwknopd[58372]: rule_exists_chk_support() Rule : '-t filter -j fwknop_input 2>&1' in tcp does not exist
Jul 24 12:57:22 morfikownia fwknopd[58372]: jump_rule_exists_chk_support() jump rule not found
Jul 24 12:57:22 morfikownia fwknopd[58372]: delete_all_chains() CMD: '(/sbin/iptables -t filter -F fwknop_input 2>&1; /sbin/iptables -t filter -X fwknop_input 2>&1)' (res: 0, err: iptables: No chain/target/match by that name.#012iptables: No chain/target/match by that name.)
Jul 24 12:57:22 morfikownia fwknopd[58372]: chain_exists() CMD: '/sbin/iptables -t filter -L fwknop_input -n 2>&1' (res: 0, err: iptables: No chain/target/match by that name.)
Jul 24 12:57:22 morfikownia fwknopd[58372]: 'filter' table 'fwknop_input' chain exists
Jul 24 12:57:22 morfikownia fwknopd[58372]: create_chain() CMD: '/sbin/iptables -t filter -N fwknop_input 2>&1' (res: 0, err: )
Jul 24 12:57:22 morfikownia fwknopd[58372]: rule_exists_chk_support() CMD: '/sbin/iptables -C tcp -t filter -j fwknop_input 2>&1' (res: 0, err: iptables: No chain/target/match by that name.)
Jul 24 12:57:22 morfikownia fwknopd[58372]: rule_exists_chk_support() Rule : '-t filter -j fwknop_input 2>&1' in tcp does not exist
Jul 24 12:57:22 morfikownia fwknopd[58372]: jump_rule_exists_chk_support() jump rule not found
Jul 24 12:57:22 morfikownia fwknopd[58372]: add_jump_rule() CMD: '/sbin/iptables -t filter -I tcp 2 -j fwknop_input 2>&1' (res: 0, err: )
Jul 24 12:57:22 morfikownia fwknopd[58372]: Added jump rule from chain: tcp to chain: fwknop_input
Jul 24 12:57:22 morfikownia fwknopd[58372]: comment_match_exists() CMD: '/sbin/iptables -t filter -I tcp 1 -s 127.0.0.2 -m comment --comment __TMPCOMMENT__ -j ACCEPT 2>&1' (res: 0, err: )
Jul 24 12:57:22 morfikownia fwknopd[58372]: iptables 'comment' match is available
Jul 24 12:57:22 morfikownia fwknopd[58372]: ipt_chk_support() CMD: '/sbin/iptables -t filter -I tcp 1 -s 127.0.0.2 -p udp -j ACCEPT 2>&1' (res: 0, err: )
Jul 24 12:57:22 morfikownia fwknopd[58372]: ipt_chk_support() CMD: '/sbin/iptables -t filter -C tcp -s 127.0.0.2 -p udp -j ACCEPT 2>&1' (res: 0, err: )
Jul 24 12:57:22 morfikownia fwknopd[58372]: ipt_chk_support() -C supported
Jul 24 12:57:22 morfikownia fwknopd[58372]: Sniffing interface: bond0
Jul 24 12:57:22 morfikownia fwknopd[58372]: PCAP filter is: 'udp port 23451'
Jul 24 12:57:22 morfikownia fwknopd[58372]: Starting fwknopd main event loop.
Niby jest trochę błędów ale są to komunikaty mające na celu dbanie o czystość łańcuchów i zachowanie porządku reguł w iptables. Generalnie nic poważnego, daemon nasłuchuje.
Sprawdźmy teraz jak port od ssh (12345) odpowie na skan syn:
# nmap -sS -p 12345 192.168.1.150
Starting Nmap 6.46 ( http://nmap.org ) at 2014-07-21 20:02 CEST
Stats: 0:00:13 elapsed; 0 hosts completed (0 up), 1 undergoing Ping Scan
Parallel DNS resolution of 1 host. Timing: About 0.00% done
Nmap scan report for 192.168.1.150
Host is up (0.00019s latency).
PORT STATE SERVICE
12345/tcp closed
Nmap done: 1 IP address (1 host up) scanned in 14.51 seconds
Port zamknięty wskazuje, że trafiliśmy w mechanizm banujący skany, przynajmniej działa. xD Zdejmijmy sobie bana by kontynuować działania:
# echo -192.168.1.20 > /proc/net/xt_recent/tcp-portscan
Teraz z maszyny klienckiej przesyłamy ciasteczko do serwera za pomocą poniższej linijki:
send_spa_packet: bytes sent: 225
root@red_viper:~# fwknop -n 192.168.1.150 -v -p 23451
SPA Field Values:
=================
Random Value: 1806382834606381
Username: morfik
Timestamp: 1406199546
FKO Version: 2.0.2
Message Type: 1 (Access msg)
Message String: 192.168.1.20,tcp/12345
Nat Access: <NULL>
Server Auth: <NULL>
Client Timeout: 0
Digest Type: 3 (SHA256)
HMAC Type: 3 (SHA256)
Encryption Type: 1 (Rijndael)
Encryption Mode: 2 (CBC)
Encoded Data: 1806382834606381:cm9vdA:1406199546:2.0.2:1:MTkyLjE2OC4xLjEsdGNwLzExMTEx
SPA Data Digest: N7iwpADDcZtMLZLfZjm24cXs97Q56naT2v5nRNg0JqI
HMAC: oQ+3nceaQIs43tb2OFVQ8PqYUV4B4MaEkJTOzqb7HEo
Final SPA Data: 9gxSUbMvXMyA1CgyeDI8T1YfAOy1rE8aYkRRTEku5PB4YkEyBU/8zfeIA7RgvXKy0unyIVYI5kWGRFlC07v6CvFdWg2IzfxB4P+rkZcCbl7PeCyg2lL5qHv5ANdn+lXUkZSm5mhRN/jrhfeLXD/DplP1iiPCrrHweDulcPtSeTipZF1VebC8aqoQ+3nceaQIs43tb2OFVQ8PqYUV4B4MaEkJTOzqb7HEo
Generating SPA packet:
protocol: udp
source port: <OS assigned>
destination port: 23451
IP/host: 192.168.1.150
send_spa_packet: bytes sent: 225
W logu serwera można zaobserwować poniższe komunikaty:
Jul 24 12:59:05 morfikownia fwknopd[58372]: (stanza #1) SPA Packet from IP: 192.168.1.20 received with access source match
Jul 24 12:59:05 morfikownia fwknopd[58372]: SPA Packet: '9gxSUbMvXMyA1CgyeDI8T1YfAOy1rE8aYkRRTEku5PB4YkEyBU/8zfeIA7RgvXKy0unyIVYI5kWGRFlC07v6CvFdWg2IzfxB4P+rkZcCbl7PeCyg2lL5qHv5ANdn+lXUkZSm5mhRN/jrhfeLXD/DplP1iiPCrrHweDulcPtSeTipZF1VebC8aqoQ+3nceaQIs43tb2OFVQ8PqYUV4B4MaEkJTOzqb7HEo'
Jul 24 12:59:05 morfikownia fwknopd[58372]: [192.168.1.20] (stanza #1) SPA Decode (res=0):
Jul 24 12:59:05 morfikownia fwknopd[58372]: SPA Field Values:#012=================#012 Random Value: 1806382834606381#012 Username: root#012 Timestamp: 1406199546#012 FKO Version: 2.0.2#012 Message Type: 1 (Access msg)#012 Message String: 192.168.1.20,tcp/12345#012 Nat Access: <NULL>#012 Server Auth: <NULL>#012 Client Timeout: 0#012 Digest Type: 3 (SHA256)#012 HMAC Type: 3 (SHA256)#012Encryption Type: 1 (Rijndael)#012Encryption Mode: 2 (CBC)#012 Encoded Data: 1806382834606381:cm9vdA:1406199546:2.0.2:1:MTkyLjE2OC4xLjEsdGNwLzExMTEx#012SPA Data Digest: N7iwpADDcZtMLZLfZjm24cXs97Q56naT2v5nRNg0JqI#012 HMAC: oQ+3nceaQIs43tb2OFVQ8PqYUV4B4MaEkJTOzqb7HEo#012 Final SPA Data: 9gxSUbMvXMyA1CgyeDI8T1YfAOy1rE8aYkRRTEku5PB4YkEyBU/8zfeIA7RgvXKy0unyIVYI5kWGRFlC07v6CvFdWg2IzfxB4P+rkZcCbl7PeCyg2lL5qHv5ANdn+lXUkZSm5mhRN/jrhfeLXD/DplP1iiPCrrHweDulcPtSeTipZF1VebC8aq
Jul 24 12:59:05 morfikownia fwknopd[58372]: chain_exists() CMD: '/sbin/iptables -t filter -L fwknop_input -n 2>&1' (res: 0, err: Chain fwknop_input (1 references)#012target prot opt source destination )
Jul 24 12:59:05 morfikownia fwknopd[58372]: 'filter' table 'fwknop_input' chain exists
Jul 24 12:59:05 morfikownia fwknopd[58372]: create_chain() CMD: '/sbin/iptables -t filter -N fwknop_input 2>&1' (res: 0, err: iptables: Chain already exists.)
Jul 24 12:59:05 morfikownia fwknopd[58372]: rule_exists_chk_support() CMD: '/sbin/iptables -C tcp -t filter -j fwknop_input 2>&1' (res: 0, err: )
Jul 24 12:59:05 morfikownia fwknopd[58372]: rule_exists_chk_support() Rule : '-t filter -j fwknop_input 2>&1' in tcp already exists
Jul 24 12:59:05 morfikownia fwknopd[58372]: jump_rule_exists_chk_support() jump rule found
Jul 24 12:59:05 morfikownia fwknopd[58372]: rule_exists_chk_support() CMD: '/sbin/iptables -C fwknop_input -t filter -p 6 -s 192.168.1.20 --dport 12345 -m comment --comment _exp_1406199565 -j ACCEPT 2>&1' (res: 0, err: iptables: Bad rule (does a matching rule exist in that chain?).)
Jul 24 12:59:05 morfikownia fwknopd[58372]: rule_exists_chk_support() Rule : '-t filter -p 6 -s 192.168.1.20 --dport 12345 -m comment --comment _exp_1406199565 -j ACCEPT 2>&1' in fwknop_input does not exist
Jul 24 12:59:05 morfikownia fwknopd[58372]: rule_exists() Rule : '-t filter -p 6 -s 192.168.1.20 --dport 12345 -m comment --comment _exp_1406199565 -j ACCEPT 2>&1' in fwknop_input does not exist
Jul 24 12:59:05 morfikownia fwknopd[58372]: create_rule() CMD: '/sbin/iptables -A fwknop_input -t filter -p 6 -s 192.168.1.20 --dport 12345 -m comment --comment _exp_1406199565 -j ACCEPT 2>&1' (res: 0, err: )
Jul 24 12:59:05 morfikownia fwknopd[58372]: create_rule() Rule: '-t filter -p 6 -s 192.168.1.20 --dport 12345 -m comment --comment _exp_1406199565 -j ACCEPT 2>&1' added to fwknop_input
Jul 24 12:59:05 morfikownia fwknopd[58372]: Added Rule to fwknop_input for 192.168.1.20, tcp/12345 expires at 1406199565
Pakiet został przechwycony, reguła dodana do iptables. Po 20s, okno powinno się automatycznie zamknąć, sprawdźmy log:
Jul 24 12:59:25 morfikownia fwknopd[58372]: check_firewall_rules() CMD: '/sbin/iptables -t filter -L fwknop_input --line-numbers -n 2>&1' (res: 0, cmd_out: Chain fwknop_input (1 references)#012num target prot opt source destination #0121 ACCEPT tcp -- 192.168.1.20 0.0.0.0/0 tcp dpt:12345 /* _exp_1406199565 */)
Jul 24 12:59:25 morfikownia fwknopd[58372]: RES=0, CMD_BUF: /sbin/iptables -t filter -L fwknop_input --line-numbers -n 2>&1#012RULES LIST: Chain fwknop_input (1 references)#012num target prot opt source destination #0121 ACCEPT tcp -- 192.168.1.20 0.0.0.0/0 tcp dpt:12345 /* _exp_1406199565 */
Jul 24 12:59:25 morfikownia fwknopd[58372]: check_firewall_rules() CMD: '/sbin/iptables -t filter -D fwknop_input 1 2>&1' (res: 0, err: )
Jul 24 12:59:25 morfikownia fwknopd[58372]: Removed rule 1 from fwknop_input with expire time of 1406199565
I reguła została usunięta odcinając tym samym dostęp do serwera.
Dzięki mechanizmowi jaki oferuje fwknop możemy spać spokojnie nawet w przypadku gdy ktoś wystrzeli publicznie z informacją o jakiejś luce 0day.