Unikanie ataków DDoS z SYNproxy

Spis treści

Internet nie jest zbyt przyjaznym miejscem i jest wielce prawdopodobne, że prędzej czy później ktoś zaatakuje jedną z naszych maszyn, która świadczy w nim jakieś usługi. Są różne typy ataków, w tym przypadku chodzi o ataki DDoS z wykorzystaniem pakietów wchodzących w proces potrójnego witania (three way handshake) przy nawiązywaniu połączenia w protokole TCP, tj. pakiety SYN , SYN-ACK i ACK . Istnieje szereg mechanizmów, które adresują problem SYN flooding'u ale żaden z nich nie jest doskonały. Jakiś czas temu, do kernela linux'owego trafił patch implementujący mechanizm SYNproxy i w tym wpisie obadamy go sobie nieco dokładniej.

Jak działa SYNproxy

Większość systemów linux'owych ma zaimplementowany firewall, który zwykle operuje na stanach połączeń. Stanów mamy z grubsza trzy: NEW, ESTABLISHED i RELATED. Wobec czego, każdy pakiet musi być przypisany do jakiegoś z tych stanów by zapora wiedziała czy ma go przepuścić czy też zablokować. Każde poprawnie nawiązane połączenie w protokole TCP zaczyna się od pakietu SYN , na który druga strona połączenia odpowiada pakietem SYN-ACK , no i po otrzymaniu tego pakietu, ta pierwsza strona wysyła pakiet ACK . Po tym procesie, połączenie zostaje ustanowione i można zacząć wymieniać informacje. Jest jednak szereg pakietów, które nie pasują do tych powyższych stanów połączeń, np. mają ustawione nieodpowiednie flagi i wtedy takie pakiety zostają zakwalifikowane do stanu INVALID, który zwykle jest blokowany na zaporze. Dokładny opis poszczególnych stanów można znaleźć we wpisie poświęconym budowie firewall'a. Może ten opisany wyżej mechanizm wydaje się być w porządku ale jego podstawową wadą jest fakt, że wszystkie połączenia, nawet te, które nie zostały jeszcze poprawnie nawiązane, są śledzone przez kernel za pomocą modułu conntrack, a to zjada sporo cennych zasobów i tu właśnie do gry wchodzi SYNproxy.

W przypadku mechanizmu SYNproxy, mamy do czynienia z dwoma etapami witania (three way handshake). Pierwszy z nich występuje na linii klient -> SYNproxy , drugi zaś na linii SYNproxy -> serwer. Mając to na uwadze, gdy klient wysyła pakiet SYN do serwera, to ten trafiając na firewall jest oznaczany jako UNTRACKED, a następnie przesyłany do SYNproxy, który odpowiada klientowi pakietem SYN-ACK również w stanie UNTRACKED. Na ten pakiet klient odpowiada pakietem ACK , który może być oznaczony na zaporze jako INVALID lub UNTRACKED. Dzięki takiemu stanowi rzeczy, nie są zjadane żadne zasoby pamięci.

Po tym jak połączenie zostanie nawiązane z SYNproxy, ten automatycznie rozpoczyna proces witania z faktycznym serwerem spoofując pakiet SYN , tak by serwer był w stanie zobaczyć, że ten oryginalny klient próbuje się podłączyć. Odbywa się to przez nawiązanie nowego połączenia przez SYNproxy, które w iptables jest łapane przez łańcuch OUTPUT. Źródłowy adres IP takiego pakietu jest podmieniany na adres IP oryginalnego klienta. Następnie serwer odpowiada pakietem SYN-ACK do tego klienta. Ten pakiet trafia jednak do SYNproxy, który odpowiada pakietem ACK i w ten sposób połączenie przechodzi w stan ESTABLISHED. Z chwilą gdy połączenie zostaje ustanowione, SYNproxy nie bierze już udziału w wymianie pakietów między klientem a serwerem.

Dostosowanie kernela pod SYNproxy

By skorzystać z mechanizmu SYNproxy musimy posiadać kernel w wersji minimum 3.12 oraz iptables w wersji minimum 1.4.21 . Innym wymaganym czynnikiem jest aktywacja mechanizmu SYN cookies, a że on korzysta ze znaczników czasów (TCP timestamp), to również i one muszą zostać aktywowane w ustawieniach kernela. Poniżej są zebrane wszystkie te rzeczy, które musimy dopisać do pliku /etc/sysctl.conf :

net.ipv4.tcp_timestamps = 1
net.netfilter.nf_conntrack_timestamp = 1
net.ipv4.tcp_syncookies = 1

Dodatkowo, musimy nieco inaczej skonfigurować sam moduł conntrack . Mianowicie, pakiety ACK nieodnoszące się do żadnego ustanowionego połączenia muszą zostać wyjęte spod mechanizmu śledzenia połączeń. Sprawi to, że takie pakiety zostaną zakwalifikowane jako INVALID i zarazem uchroni nas od zalewów pakietów ACK (ACK flood). Do pliku /etc/sysctl.conf dopisujemy zatem również i ten poniższy parametr:

net.netfilter.nf_conntrack_tcp_loose = 0

By zmiany zostały zaaplikowane, musimy jako administrator jeszcze wpisać w terminalu sysctl -p .

Implementacja SYNproxy w iptables

Będziemy operować na dwóch tablicach: raw oraz filter . Reguły są w sumie trzy. Pierwszą z nich dodajemy do tablicy raw , a to z tego względu, że pakiety, które do niej trafiają, nie podpadają jeszcze pod mechanizm śledzenia połączeń. Dopasowanie jest dowolne, tylko trzeba pamiętać, że protokół, na którym operujemy to TCP . Z kolei, target , który musimy określić dla tej reguły to -j NOTRACK . Informuje on system, by pakietów złapanych przez tę regułę nie śledził. Poniżej przykładowa reguła:

iptables -t raw -A PREROUTING -i eth0 -p tcp --dport 80 --syn -j NOTRACK

Poniższa reguła zaś wędruje do tablicy filter .

iptables -I INPUT -p tcp -m tcp -m conntrack --ctstate INVALID,UNTRACKED -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460

Ma ona za zadanie złapanie wszelkich pakietów w stanie INVALID (między innymi ACK ) oraz UNTRACKED (pakiety SYN ) i przesłanie ich do SYNproxy.

Ostatnia reguła raczej powinna być już standardowo na naszej zaporze, bo odpowiada ona za blokowanie pakietów w stanie INVALID ale moim zdaniem powinniśmy ją umieścić bezpośrednio za regułami z SYNproxy. Poniżej ta reguła:

iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

Reguły w iptables powinny łapać pakiety. Jeśli liczniki biją, znaczy, że wszystko działa jak należy. By mieć absolutną pewność, że mechanizm SYNproxy działa, możemy także zajrzeć do pliku /proc/net/stat/synproxy , który powinien pokazać nam informacje na temat otrzymanych pakietów SYN i ilości ważnych i nieważnych ciasteczek.

Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.