Apache2: Moduł evasive, ipset i iptables (anty DOS/DDOS)

Spis treści

Apache2 ma kilka ciekawych modułów, które mogą uchronić nasz serwer www przed atakami DOS i DDOS. Jednym z nich jest moduł evasive . Nie jest on jednak oficjalnym modułem i brak o nim jakiejkolwiek wzmianki w oficjalnej dokumentacji na stronie Apache2. Niemniej jednak, jest to bardzo prosty moduł składający się dosłownie z kilku dyrektyw, które są w stanie zablokować zapytania o zasoby serwera w przypadku, gdy zostanie przekroczony pewien ustalony przez nas limit. Dodatkowo, ten moduł może współgrać z filtrem iptables oraz ipset , dając nam możliwość wygodnego blokowania uporczywych klientów na poziomie pakietów sieciowych.

Pozyskanie modułu evasive

Jako, że moduł evasive nie jest częścią projektu Apache2, to trzeba go pozyskać z zewnętrznego źródła. Debian dysponuje nawet odpowiednim pakietem, który wystarczy doinstalować. Chodzi o libapache2-mod-evasive . Po instalacji, moduł trzeba aktywować w poniższy sposób:

# a2enmod evasive
# systemctl restart apache2

Dokumentacja modułu znajduje się na blogu autora. Warto także zajrzeć do pliku /usr/share/doc/libapache2-mod-evasive/README.gz .

Zasada działania mechanizmu blokującego

W chwili nadejścia zapytania do serwera www, jest ono przetwarzane przez moduł evasive . Na początku jest sprawdzany adres IP klienta, który wysłał zapytanie. Ten adres jest porównywany z tymi, które trafiły na tymczasową czarną listę. W przypadku, gdy adres nie zostanie odnaleziony na liście, moduł utworzy klucz w tablicy hashów, którym będzie powiązanie adresu IP z żądanym adresem URI. Następnie ta tablica jest przeszukiwana w celu ustalenia czy jakiś host odpytał dany zasób (lub szereg obiektów) kilka razy w ciągu określonego interwału (domyślnie 1s). Gdy tak się zdarzy w istocie, adres IP tego klienta trafi na czarną listę przez pewien okres czasu. Wszystkie następne zapytania od tego klienta będą kwitowane kodem błędu 403 (Forbidden). W przypadku, gdy klient nie zaprzestanie próby podłączenia, czas bana ulegnie odświeżeniu. Te komunikaty 403 zjadają zasoby procesora i łącza. Dlatego też powinniśmy zaprzęgnąć iptables do pracy.

Każde zdarzenie zablokowania adresu IP, zostawia ślad w logu systemowym, który wygląda mniej więcej tak jak ten komunikat poniżej:

mod_evasive[31034]: Blacklisting address 66.102.9.105: possible DoS attack.

Dyrektywy modułu evasive

Cała konfiguracja dla modułu evasive jest umieszczona w pliku /etc/apache2/mods-enabled/evasive.conf . Przejdźmy zatem do edycji tego pliku, by zobaczyć co tam się znajduje:

<IfModule mod_evasive20.c>
    DOSHashTableSize    3079
    DOSPageCount        2
    DOSSiteCount        50
    DOSPageInterval     1
    DOSSiteInterval     1
    DOSBlockingPeriod   10

    #DOSEmailNotify      you@yourdomain.com
    #DOSSystemCommand    "su - someuser -c '/sbin/... %s ...'"
    #DOSLogDir           "/var/log/mod_evasive"
</IfModule>

Im większy rozmiar tablicy hashów określimy w dyrektywie DOSHashTableSize , tym więcej pamięci ona będzie utylizować ale też lepiej będzie działał cały mechanizm, bo nie trzeba usuwać rekordów z tej tablicy przed dodaniem nowych. Każdy proces serwera Apache2 (domyślnie 5-100), ma osobną tablicę hashów. Nie zaleca się zwiększania tego parametru, no chyba, że nasz serwer jest sporych rozmiarów i realizuje naprawdę wiele zapytań. Jeśli wartość, którą określimy nie będzie liczbą pierwszą, to zostanie ona automatycznie wybrana w oparciu o tę poniższą listę:

53         97         193       389       769
1543       3079       6151      12289     24593
49157      98317      196613    393241    786433
1572869    3145739    6291469   12582917  25165843
50331653   100663319  201326611 402653189 805306457
1610612741 3221225473 4294967291

Dyrektywy DOSPageInterval i DOSPageCount ustanawiają limit żądań w stosunku do konkretnego zasobu na serwerze (URI). W tym przypadku, każdy adres IP może wykonać maksymalnie dwa żądania w ciągu 1 sekundy. Z kolei zaś dyrektywy DOSSiteInterval oraz DOSSiteCount precyzują limit dla całego serwisu. Zatem każdy adres IP może wysłać maksymalnie 50 zapytań pod różne zasoby serwera. Po przekroczeniu tych limitów, zapytania z tego adresu IP zostaną zablokowane na okres zdefiniowany w DOSBlockingPeriod , czyli 10 sekund.

Dyrektywa DOSEmailNotify jest w stanie nas powiadomić drogą mailową, za każdym razem, gdy jakiś adres IP zostanie zablokowany. Z kolei DOSLogDir określa tymczasowy katalog wykorzystywany przez mechanizm blokujący. Standardowo jest folder /tmp/ , a pliki tworzone w nim mają takie uprawnienia na jakich operuje serwer Apache2. Domyślnie są to www-data:www-data . Za każdym razem, gdy jakiś adres IP wyjdzie poza ustanowione limity, to zostanie w tym katalogu utworzony plik o nazwie dos-1.2.3.4 , gdzie 1.2.3.4 , to zablokowany adres IP. Te pliki nie są usuwane, czego efektem jest blokada mechanizmu obronnego w stosunku do uprzednio zablokowanego adresu IP. By znów zablokować dany adres, ten plik musi być pierw usunięty (o tym będzie dalej).

Połączenie modułu evasive z iptables i ipset

Pozostała nam do omówienia ostatnia z dyrektyw, które widnieją w pliku /etc/apache2/mods-enabled/evasive.conf . Jest to DOSSystemCommand , czyli polecenie, które zostanie uruchomione w przypadku, gdy jakiś klient przekroczy limit zapytań. Najprostszym i zarazem najefektywniejszym rozwiązaniem jest stworzenie listy dla ipset'a i dynamiczne dodawanie do niej adresów IP. Wymagane jest jednak dodatkowe oprogramowanie w postaci pakietu ipset , które musimy zainstalować na debianie. Będzie nam też potrzebne sudo , bo inaczej nie dodamy adresów do listy, gdyż serwer Apache2 nie działa domyślnie z prawami administratora systemu.

Następnie musimy stworzyć listę, na którą będą trafiać pakiety:

# ipset create evasive hash:net family inet maxelem 5000 --timeout 60

Teraz podpinamy listę pod filtr iptables :

# iptables -t raw -N evasive-in
# iptables -t raw -N evasive-out

# iptables -t raw -A PREROUTING -j evasive-in
# iptables -t raw -A OUTPUT -j evasive-out

# iptables -t raw -A evasive-in -m set --match-set evasive src -j DROP
# iptables -t raw -A evasive-out -m set --match-set evasive dst -j DROP

Set podpięty pod firewall. Wszystko czego nam teraz potrzeba, to polecenie, które doda adresy. To polecenie musimy również uwzględnić w sudo . Edytujemy zatem konfigurację sudo za pomocą visudo i dodajemy tam poniższe wpisy:

Defaults!/sbin/ipset !requiretty
Host_Alias HOSTY = localhost,domena.com
www-data    HOSTY = (root) NOPASSWD: /sbin/ipset add evasive * -exist timeout 60

Teraz to samo polecenie, wpisujemy do pliku /etc/apache2/mods-enabled/evasive.conf w dyrektywie DOSSystemCommand , z tym, że korzystamy z sudo :

DOSSystemCommand "sudo ipset add evasive %s -exist timeout 60 && sleep 1 && rm /tmp/dos-%s"

Wyżej pojawił się znaczek %s . Odpowiada on za adres IP, który moduł evasive zwróci po przekroczeniu limitu zapytań. Po znaku && mamy drugie polecenie, które spowolni wykonanie trzeciego polecenia. Chodzi o to, że klient może wysłać kilka zapytań i one wszystkie zostaną przetworzone przez moduł. W efekcie pojawi się bardzo ekstensywny log, którego nie chcemy. Dlatego właśnie czekamy 1s, aż mechanizm zapory zablokuje pakiety. Wtedy blokadę można zdjąć (usunąć plik), bo już żadne pakiety od tego klienta nie będą do nas docierać. Od tej chwili blokować będzie go iptables . Wartość 60, to 60 sekund, przez taki okres czasu adres IP będzie widniał na liście ipset'a. Jeśli klient nie dokona żadnych akcji w tym czasie, to adres z tej listy zostanie usunięty. Jeśli jednak, będzie próbował nawiązać połączenie, to czas ulegnie odświeżeniu.

Test ataku DOS/DDOS

Przydałoby się teraz przetestować jak ten mechanizm anty DOS/DDOS działa. Możemy to zrobić na dwa sposoby. Pierwszy z nich, to wykorzystanie skryptu dostarczanego z pakietem libapache2-mod-evasive . Znajduje się on pod /usr/share/doc/libapache2-mod-evasive/examples/test.pl . Wystarczy go przekopiować na maszynę kliencką i nieco dostosować. Generalnie chodzi o zmianę adresu:

...
PeerAddr=> "1.2.3.4:80");
...

Teraz ten skrypt można uruchomić kilka razy i po chwili nasz adres IP powinien trafić na listę ipset'a. Do wglądu przez ipset list .

Drugą opcją jest obniżenie wartości dyrektyw DOSPageCount i DOSSiteCount do 1 oraz podbicie wartości w dyrektywach DOSPageInterval i DOSSiteInterval do 5. Wtedy dość energiczne odwiedzanie strony via curl wyzwoli mechanizm blokujący.

Jeśli adres IP nie jest dodawany do listy ipset'a, to prawdopodobnie mamy pozostałości po wcześniejszych atakach DOS/DDOS, tj. pliki dos-* w katalogu /tmp/ . Musimy je usunąć usunąć.

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.