W przypadku ataków DoS nasz Apache szybko może odmówić współpracy, przetwarzając setki zbędnych requestów, skutecznie zabijając serwer. Fajnym rozwiązaniem problemu jest mod_evasive, który monitoruje ilość przychodzących połączeń http i blokuje najbardziej natarczywych klientów.
Jak to właściwie działa? po otrzymaniu requesta mod_evasive:
- sprawdza czy adres nie jest na czarnej liście - jeśli tak, odrzuca z kodem błędu 403
- wykonuje hasha z uri i IP i sprawdza jego częstotliwość występowania, czyli znajduje potencjalne ataki na konretny uri
- wykonuje hasha z IP i odrzuca ataki na całą witrynę ze zmiennym uri
- jeśli powyższe testy przeszły pozytywnie, puszcza request dalej, podnosząc liczniki i zapisując hashe, zapamiętując tym samym aktywność klienta.
0 1 2 3 4 5 6 7 8 9 10 11 |
LoadModule evasive_module modules/mod_evasive.so DOSHashTableSize 3097 # rozmiar tablicy z zapamiętanymi kluczami DOSPageCount 5 # l. req do pojedynczej strony dla DOSPageInterval DOSSiteCount 100 # l. req do całego serwisu dla DOSSiteInterval DOSPageInterval 2 # przedział czasowy pojedynczej strony DOSSiteInterval 2 # przedział czasowy całego serwisu DOSBlockingPeriod 10 # czas blokady, w sekundach DOSCloseSocket yes # zamyka sockety po zablokowaniu, # wymuszając ponowne nawiązanie połączenia |
Powyższy config powoduje, że każdy klient który wykona więcej niż 5req/2s do jednego uri lub 100req/2s do całego serwisu zostanie zablokowany na 10s.
W paczce z mod_evasive znajduje się prosty skrypt test.pl, w którym podmieniamy 127.0.0.1 na adres na którym słucha apache:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/perl # test.pl: small script to test mod_dosevasive's effectiveness use IO::Socket; use strict; for(0..100) { my($response); my($SOCKET) = new IO::Socket::INET( Proto => "tcp", PeerAddr=> "127.0.0.1:80"); if (! defined $SOCKET) { die $!; } print $SOCKET "GET /?$_ HTTP/1.0nn"; $response = <$SOCKET>; print $response; close($SOCKET); } |
Testujemy blokowanie:
0 1 2 3 4 5 6 7 8 9 10 11 12 |
rootbox mod_evasive # chmod +x ./test.pl rootbox mod_evasive # ./test.pl HTTP/1.1 200 OK HTTP/1.1 200 OK (...) HTTP/1.1 200 OK HTTP/1.1 200 OK HTTP/1.1 403 Forbidden HTTP/1.1 403 Forbidden HTTP/1.1 403 Forbidden HTTP/1.1 403 Forbidden |
Domyślnie blokowanie następuje na poziomie Apache'a, czyli każdy request (nawet ten zablokowany) musi zostać obsłużony przez proces apache. Można tego uniknąć i odciążyć system, przez wyblokowanie odrzucanych requestów bezpośrednio na firewallu.
mod_evasive pozwala na wywołanie zewnętrznej komendy w momencie zablokowania klienta. Przykładowo:
0 1 2 |
DOSSystemCommand "/var/tools/evasive_fw.sh %s" |
0 1 2 3 4 5 6 7 |
#!/bin/bash # drop na porcie 80 sudo /sbin/iptables -I INPUT -p tcp --syn --dport 80 -s $1 -j DROP # utrwalamy konfigurację, zrzut do pliku którego używasz # jako źródła reguł przy starcie iptables /sbin/iptables-save > /var/lib/iptables/rules-save |
Na koniec należy dodać, że z ustawieniami mod_evasive należy bardzo uważać. Ruch który dla jednego serwisu wygląda na DDoS, dla innego może nie być żadną anomalią, zatem zalecam ostrożność przy tuningu parametrów modułu, a szczególną przy automatyzowaniu wycinania ruchu na fw :)
Dobrym pomysłem na rozszerzenie funkcjonalności tego rozwiązania jest obsługa timeoutów dla reguł, na przykład za pomocą wywoływanego cyklicznie parsera zablokowanych adresów. Skryptem wykonywanym przez DOSSystemCommand zapisujemy do pliku timestamp i blokowany IP, a wywoływanym z crona poprawiamy reguły fw aby zgadzały się z listą generowaną powyżej. Ale to już inna historia :)