Odrzucamy trochę szajsu krążącego po stronach internetowych
Napisał: Patryk Krawaczyński
27/10/2020 w Bezpieczeństwo Brak komentarzy. (artykuł nr 752, ilość słów: 2125)
O
pisana tutaj metoda nie jest najbardziej zaawansowaną techniką obronną małych instalacji serwerów WWW na świecie, ale bazuje ona na bardzo prostym mechanizmie, który przy spełnieniu jednego warunku potrafi zablokować trochę szajsu krążącego po internecie. Zacznijmy od charakterystyki ruchu w internecie. Bardzo często porównywany on jest do infostrady, po której ciągle pędzą jakieś dane. Wyobrazimy teraz sobie, że internet to jedna wielka autostrada ułożona w linii prostej, a każdy z niej zjazd prowadzi do jakiegoś serwera. Na długości całej autostrady, jak i przy każdym zjeździe poprowadzone są osłony energochłonne. Poprawny ruch do serwera powinien zawsze odbywać się tylko i wyłącznie po wyznaczonej drodze – bez dotykania barier.
Z naszej strony musimy zapewnić, aby wyznaczona droga była czysta i pozbawiona dziur – tak, aby ona sama nie powodowała możliwości uderzenia podróżującego do naszego serwera w bariery. Jeśli przyjmiemy, że czystą drogą są kody HTTP 2xx oraz 3xx, a każde uderzenie w bariery powoduje 4xx bardzo prosto będziemy w stanie usłyszeć piratów drogowych nadjeżdżających do naszego serwera. Dlatego, jeśli jakieś stare linki i adresy powodują w naszej webaplikacji kody 4xx (najczęściej: 404) na początek należy je wyeliminować np. poprzez stałe lub tymczasowe przekierowania. Musimy doprowadzić do stanu, gdzie każdy poprawny i świadomy klient nie będzie napotykał na tego rodzaju kody przy normalnym użytkowaniu strony.
Posiadając już czystą drogę wystarczy teraz zainstalować system alarmowy podpięty do naszych barier energochłonnych, który będzie automatycznie zamykał bramę do naszego serwera, jeśli liczba uderzeń będzie przekraczała określoną przez nas liczbę. Najczęściej spotykanym narzędziem na pojedynczych serwerach jest fail2ban – narzędzie to skanuje pliki dzienników różnych daemonów systemowych (np. /var/log/apache/error_log
) i blokuje adresy IP, które wykazują wrogie zamiary – generują wiele błędów uwierzytelniania, próbkują sploity itp. Możemy użyć go do aktualizacji reguł zapory sieciowej w celu blokady takich adresów przez określony czas lub skonfigurować dowolne inne działanie (np. wykonanie żądania HTTP do innego systemu, czy wysłanie wiadomości e-mail).
Pierwszym krokiem jest instalacja pakietu:
apt install -y fail2ban
Następnie definiujemy sposób wykrywania i blokowania piratów do naszej infostrady:
rm /etc/fail2ban/jail.d/defaults-debian.conf touch /etc/fail2ban/jail.d/jail.local
Przykładowa zawartość pliku jail.local
może wyglądać np. tak:
[DEFAULT] ignoreip = 127.0.0.1/8 10.10.10.1/32 [apache-badcodes] enabled = true logpath = /var/log/apache2/nfsec_ssl_access.log action = iptables[port="https"] abuseipdb[norestored=1] findtime = 300 maxretry = 5 bantime = 2592000
- ignoreip – lista dostępu wykluczająca wszystkie adresy IP z reguł. Tutaj powinniśmy zazwyczaj umieścić wszystkie adresy IP, które nasz serwer posiada przypisane do swoich interfejsów. Dodatkowo na tej liście możemy umieścić zaufane maszyny, z których łączymy się do serwera lub testujemy z nich różne zmiany w kodzie webaplikacji.
- logpath – odnosi się do ścieżek plików, które będą przeszukiwane pod kątem wystąpienia różnych wzorców, zdefiniowanych w filtrze o nazwie apache-badcodes (jego zawartość poznamy później).
- action – jeśli nasz filtr znajdzie w zdefiniowanym pliku dopasowanie musimy określić akcję, która ma zostać podjęta. W powyższym przykładzie wykonujemy dwie czynności. Pierwsza blokuje dalszy dostęp do serwera po protokole HTTPS (port 443) wyzwalając akcje zdefiniowaną w
/etc/fail2ban/action.d/iptables.conf
. Druga, równie ważna – wysyła informacje o wrogim adresie IP do serwisu AbuseIPDB, ostrzegając tym samym innych jego użytkowników o nowym zagrożeniu. - findtime – przedział czasu z przeszłości, do którego ma się cofnąć filtr w celu wyłapania wzorców. Nie należy mylić tego parametru z czasem, co jaki jest sprawdzany plik z logami. Czyli jeśli teraz jest 17.10.2020 godzina 23:55:00, a my ustawiliśmy
findtime
na 5 minut (300 sekund), to filtr przeszuka wszystkie wpisy z plikulogpath
od godziny 23:50:00 do 23:55:00 – czyli cofnie się o 5 minut w przeszłość i będzie się starał znaleźć dopasowania do naszego filtra. - maxretry – ile razy pirat może uderzyć w naszą barierę energochłonną w danym oknie czasowym zdefiniowanym w
findtime
. Czyli, jeśli ktoś przywali nam 5 lub więcej razy w ciągu 5 minut to brama do naszego serwera się zamknie i zostanie odnotowana próba wtargnięcia w prywatnej agencji bezpieczeństwa AbuseIP. - bantime – jak długo dany adres IP ma zostać przez nas trzymany w bazie wrogich obiektów, które są źródłem dla różnych akcji. 2592000, czyli 30 dni. Jeśli nasz serwer zostanie zrestartowany po 15 dniach to fail2ban i tak ponownie wczyta taki adres do zapory ogniowej w celu dalszej blokady.
W zależności od tego, jak bardzo chcemy być dokładni i na jak agresywne skanowania chcemy reagować – różne wartości dla findtime oraz maxretry są w stanie zapewnić nam końcowy efekt. Na przykład f = 3600 oraz m = 1, spowoduje, że będziemy zbierać bardzo dużo adresów IP, ale nie koniecznie wszystkie z nich będą posiadały wrogie zamiary. Wystarczy jeden źle zdefiniowany link powodujący kod 404 którym podąży legalny użytkownik w przeglądarce, a zostanie on zablokowany. Z kolei ustalając f = 60 oraz m = 10 nastawimy się na automaty, które bardzo dynamicznie skanują swoje cele. Dlatego uruchamiając pierwszy raz przyjęte reguły najlepiej ustawić akcję na dummy i sprawdzać zawartość pliku /var/run/fail2ban/fail2ban.dummy
oraz logi fail2ban w ścieżce /var/log/fail2ban.log
. Następnie odnaleźć aktywności danych adresów IP w logach serwera WWW i określić, czy zamiar był rzeczywiście nieprzyjacielski, czy czasami nie należy poprawić jakieś funkcjonalności / parametru z naszej strony.
Wróćmy teraz do naszego filtra. Nazwaliśmy go [apache-badcodes] – z tego powodu fail2ban będzie go szukał w ścieżce: /etc/fail2ban/filter.d/apache-badcodes.conf
. Jego początkowa postać może wyglądać następująco:
# fail2ban apache code filter # this filter is for access.log, NOT for error.log [Definition] failregex = ^<HOST> - - .* "(GET|POST|HEAD) .* (404|403|400|405) \d+ .*$ ignoreregex = ^<HOST> - - .* "(GET|POST|HEAD) .*\.(jpg|png|gif) HTTP.* 404 \d+ .*$ datepattern = ^[^\[]*\[({DATE}) {^LN-BEG} # author: agresor / nfsec.pl
- failregex – określamy wyrażenie regularne, które będzie nam łapało dopasowania w logach oraz przekazywało interesujący nas człon – tutaj
<HOST>
będący adresem IP jako obiekt, na którym zostanie wykonana przyjęta przez nas akcja (blokady na zaporze, powiadomienia e-mail itd.). - ignoreregex – przeciwieństwo
failregex
, czyli wszystkie dopasowania zostaną wykluczone i nie będą powodowały wywołania akcji. - datepattern – wzór / wyrażenie dopasowania daty – oprócz domyślnych detektorów dat możliwe jest zdefiniowane własnych formatów np.
%Y-%m-%d %H:%M(?::%S)?
– lista prawidłowych dyrektyw znajduje się w dokumentacji biblioteki strptime języka Python.
Pakiet f2b oferuje nam narzędzie fail2ban-regex za pomocą którego możemy aktywnie testować nasze wzory i wyrażenia bez konieczności uruchamiania daemona fail2ban. Wystarczy podać plik z logami, na którym chcemy przetestować dany filtr:
fail2ban-regex /var/log/apache2/access.log \ /etc/fail2ban/filter.d/apache-badcodes.conf --print-all-matched
Running tests ============= Use failregex filter file : apache-badcodes, basedir: /etc/fail2ban Use datepattern : Default Detectors Use log file : /var/log/apache2/access.log Use encoding : UTF-8 Results ======= Failregex: 25 total |- #) [# of hits] regular expression | 1) [25] ^- - .* "(GET|POST|HEAD) .* (404|403|400|405) \d+ .*$ `- Ignoreregex: 0 total Date template hits: |- [# of hits] date format | [51] ^[^\[]*\[(Day(?P<_sep>[-/])MON(?P=_sep)ExYear[ :]?24hour: | Minute:Second(?:\.Microseconds)?(?: Zone offset)?) `- Lines: 51 lines, 0 ignored, 25 matched, 26 missed [processed in 0.05 sec]
Po znalezieniu dopasowań możemy je osobiście sprawdzić czy rzeczyście spełniają one założone kryteria:
| 51... - - [20/Oct/2020:14:33:22 +0200] "GET /status?full=true HTTP/1.1" 404 390 "-" | 51... - - [20/Oct/2020:14:33:24 +0200] "GET /jmx-console HTTP/1.1" 404 390 "-" | 51... - - [20/Oct/2020:14:33:24 +0200] "GET /manager/html HTTP/1.1" 404 390 "-" | 51... - - [20/Oct/2020:14:33:25 +0200] "GET //administrator HTTP/1.1" 404 390 "-" | 51... - - [20/Oct/2020:14:33:26 +0200] "GET /joomla/administrator HTTP/1.1" 404 390 "-" | 51... - - [20/Oct/2020:14:49:31 +0200] "GET /ReportServer HTTP/1.1" 404 371 "-"
Negatywem tego zachowania będzie przełącznik --print-all-missed
. Po osiągnięciu satysfakcjonujących wyników dla zdefiniowanego filtru możemy przejść do akcji, które zostaną podjęte po wyłapaniu prób ataków. W naszym przykładzie pierwszą akcją jest iptables[port=”https”], czyli blokada portu HTTPS (443) z poziomu filtra pakietów. Jeśli posiadamy uniwersalne przekierowania z portu HTTP (80) na HTTPS możemy zmodyfikować reguły z pliku /etc/fail2ban/action.d/iptables.conf
tak, aby zakres blokowanych portów zaczynał się od 80, a na 443 kończył:
actionstart = <iptables> -N f2b-<name> <iptables> -A f2b-<name> -j <returntype> <iptables> -I <chain> -p <protocol> --dport 80:<port> -j f2b-<name> actionstop = <iptables> -D <chain> -p <protocol> --dport 80:<port> -j f2b-<name> <actionflush> <iptables> -X f2b-<name>
Ochrona serwera to jedno z zadań, które ma spełniać nasz system. Drugim jest ostrzeżenie innych, że w okolicy pojawił się pirat drogowy. Dlatego możemy zintegrować się z serwisem AbuseIPDB – jest to centralne repozytorium dla administratorów systemów i bezpieczników, którzy są zainteresowani zgłaszaniem adresów IP, które były powiązane ze złośliwą aktywnością w internecie. Centralne bazy danych oparte na reputacji swój początek miały dla systemów SMTP (ang. Simple Mail Transfer Protocol), które walczyły z falami SPAMu. Choć mają swoje wady to pozwalają na budowę list, które można szybko adaptować do masowej blokady wrogich aktywności. Im więcej systemów raportuje do takich baz tym ich wiarygodność jest większa. Przykładowy wpis w action.d/abuseipdb.conf
:
[Definition] norestored = 1 actionban = lgm=$(printf '%%.900s\n...' "<matches>"); \ curl -sSf "https://api.abuseipdb.com/api/v2/report" \ -H "Accept: application/json" -H "Key: <abuseipdb_apikey>" \ --data-urlencode "comment=$lgm" --data-urlencode "ip=<ip>" \ --data "categories=<abuseipdb_category>" actionunban = curl -XDELETE -sSf "https://api.abuseipdb.com/api/v2/clear-address" \ -H "Accept: application/json" -H "Key: <abuseipdb_apikey>" \ --data-urlencode "ipAddress=<ip>" [Init] abuseipdb_apikey = f3727543... abuseipdb_category = 21
Istotną opcją jest norestored – powoduje ona, że po restarcie daemona fail2ban lub całego serwera tylko reguły iptables zostaną odtworzone do stanu z przed restartu. Gdybyśmy nie ustawili jej dla akcji AbuseIPDB doszłoby do sytuacji, w której adres IP złapany np. 7 dni wcześniej zostałby ponownie zgłoszony w dniu restartu z datą wsteczną w logach.
Oprócz AbuseIPDB możemy wyobrazić sobie inny dowolny punkt końcowy, do którego wysyłamy wrogie adresy IP. Możemy wystawić w naszej adresacji sieciowej serwer typu honeypot, który będzie wyłapywał różnego rodzaju szkodliwą aktywność i przesyłał jej źródło do naszej wewnętrznej bazy. Inne systemy dostępne publicznie mogą cyklicznie pobierać z niej wrogie adresy i blokować z nimi komunikację na wybranych lub wszystkich portach. Jeśli zdecydowaliśmy się na konto w serwisie AbuseIPDB możemy też korzystać z informacji publikowanych przez inne serwery. Poniższy skrypt pobiera listę wszystkich adresów, które mają ocenę na poziomie 100 (skala nadużyć znajduje się w przedziale od 0 do 100 – zatem ocena 100 oznacza, że można mieć pewność, że adres IP jest złośliwy, a ocena 0 oznacza, że nie ma powodu, aby go podejrzewać o szkodliwą działalność):
#!/bin/bash IPT="/sbin/iptables" IPTS="/sbin/iptables-save" FILE="/tmp/abuseip_rules.txt" $IPTS > $FILE CHAIN=`cat $FILE | grep ABUSEIP | wc -l` if [ $CHAIN -eq "0" ]; then echo "Łańcuch ABUSEIP nie istnieje." $IPT -N ABUSEIP $IPT -I INPUT -j ABUSEIP curl -G https://api.abuseipdb.com/api/v2/blacklist -d confidenceMinimum=100 \ -H "Key: f3727543..." -H "Accept: text/plain" | egrep ^[0-9]+\. | xargs -iX \ -n 1 iptables -A ABUSEIP -s X -i eth0 -j DROP else echo "Łańcuch ABUSEIP istnieje." $IPT -F ABUSEIP curl -G https://api.abuseipdb.com/api/v2/blacklist -d confidenceMinimum=100 \ -H "Key: f3727543..." -H "Accept: text/plain" | egrep ^[0-9]+\. | xargs -iX \ -n 1 iptables -A ABUSEIP -s X -i eth0 -j DROP fi rm $FILE exit 0
Skrypt możemy uruchamiać cyklicznie, co jakiś czas (np. jeden dzień), aby zawsze posiadać aktualny obraz wrogich adresów IP. Jeden dzień po zaimportowaniu listy możemy sprawdzić liczniki zablokowanych adresów poleceniem iptables -nvL ABUSEIP | less
:
309 12360 DROP all -- eth0 * 94.102.51.XX 0.0.0.0/0 66 3432 DROP all -- eth0 * 141.98.10.XXX 0.0.0.0/0 43 1720 DROP all -- eth0 * 45.129.33.XX 0.0.0.0/0 100 6000 DROP all -- eth0 * 185.191.171.XX 0.0.0.0/0 84 4368 DROP all -- eth0 * 185.36.81.XX 0.0.0.0/0 177 7080 DROP all -- eth0 * 94.102.51.XX 0.0.0.0/0
User nfsec.pl, the webmaster of nfsec.pl, joined AbuseIPDB in April 2019 and has reported 6,048* IP addresses.
Więcej informacji: AbuseIPDB API,