Skupmy się na systemach IPS (NIPS). Z względu na sposób, w jaki blokują niepożądane pakiety możemy je podzielić na dwie grupy:
- pracujące w trybie outline (aktywne przeciwdziałanie intruzom)
- pracujące w trybie inline (zapobieganie intruzom)
Pierwszy z nich outline - bardzo rzadko używany termin, działa poza linią przepływu pakietów. Ma to miejsce, gdy odpowiednie reguły zapory ogniowej lub usługi systemowe generują logi, które są podstawą działania takiego systemu. Czyli system IPS outline podejmuje przeciwdziałanie po co najmniej pierwszym wystąpieniu niepożądanego zdarzenia.
Systemy IPS w trybie inline są bardzo pożądane, pracują dosłownie na drodze pakietów i potrafią podejmować decyzje dotyczące zablokowania bądź modyfikacji konkretnego pakietu. Ich największą zaletą jest to, że powstrzymają niepożądane pakiety, zanim te dotrą do celu. Na przykład serwer BIND może być zaatakowany pojedynczym pakietem UDP - w takiej sytuacji system aktywnego przeciwdziałania nie miały by szans.
Jednak istnieją zadania, do których systemy outline są lepsze od systemów działających na linii. Jednym z takich przykładów są boty próbujące w brutalny sposób odgadnąć hasło na serwerze SMTP. Obecnie spam jest największym zagrożeniem internetu[4]. Problem z takimi incydentami polega na tym, że pierwsze połączenie z określonego adresu IP, które dokonało niepoprawnej autoryzacji do systemu SMTP wcale nie musi być powodem do zablokowania tego adresu. Może się zdarzyć, że użytkownik źle wpisał hasło. Poza tym blokowanie adresu IP zawsze jest ryzykowne - może to być adres routera sieci i wtedy zablokujemy nawet setki innych hostów.
W takich sytuacjach doskonale sprawdzają się systemy IPS typu outline. Czytają logi i interpretują je. Jak wspomniałem błędna autoryzacja nie oznacza ataku, ale wielokrotna błędna autoryzacja z tego samego adresu IP na dodatek występująca z bardzo dużą częstotliwością jest strzałem w dziesiątkę dla takiego systemu i powodem do działania.
Oto przykład autentycznego ataku z pliku /var/log/auth.log:
Mar 30 05:19:01 prv dovecot-auth: pam_unix(dovecot:auth): check pass; user unknown
Mar 30 05:19:01 prv dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=admin@ rhost=202.55.172.6
Mar 30 05:19:05 prv dovecot-auth: pam_unix(dovecot:auth): check pass; user unknown
Mar 30 05:19:05 prv dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=test@ rhost=202.55.172.6
Mar 30 05:19:09 prv dovecot-auth: pam_unix(dovecot:auth): check pass; user unknown
Mar 30 05:19:09 prv dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=danny@ rhost=202.55.172.6
Mar 30 05:19:13 prv dovecot-auth: pam_unix(dovecot:auth): check pass; user unknown
Mar 30 05:19:13 prv dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=sharon@ rhost=202.55.172.6
Logi dotyczą demona Dovecot[5] ponieważ Postfix[6] jest tak skonfigurowany aby używał jego mechanizmu autoryzacji. Jak widzimy z logów w ciągu 13 sekund miała miejsce nieprawidłowa autoryzacja z tego samego adresu IP - nie mógł być to przypadek (dla przykładu wkleiłem tylko 8 linii, w rzeczywistości było ich ponad 200 lub 300!).
Teraz już mniej więcej wiemy z czym próbujemy walczyć. W tym celu wykorzystamy własny system IPS napisany w języku Python działających w roli aktywnego przeciwdziałania intruzom.
Jaka jest idea takiego systemu i zakres działania?
System sam generuje wiele logów, więc nasz skrypt będzie czytał i interpretował te logi, które nas interesują. Logi możemy tworzyć również sami przy użyciu zapory ogniowej iptables i jej celu zwanego LOG[7].
Teraz system IPS musi wiedzieć czego szukać i co zrobić jeżeli będzie miało miejsce takie zdarzenie. Zanim jednak przejdziemy do tego jak to zrealizować zastanówmy się nad ogólną logiką programu komputerowego.
Działanie każdego programu komputerowego można przedstawić w trzech fazach:
- pobieranie danych (input)
- przetwarzanie danych (prcessing)
- generowanie wyników (output)
Jest to kwestia zasadnicza, od niej zależy czas reakcji działania systemu IPS na zaistniałe niepożądane zdarzenia. Przedstawiona implementacja jest drugą opracowaną przez mnie. Pierwsza pobierała logi czytając je okresowo bezpośrednio z plików źródłowych. Okres trwał 11 sekund, więc czas reakcji mógł wynosić "aż" 11 sekund od dopasowania reguły/zdarzenia. W bieżącej wersji czas ten został skrócony do minimum poprzez wykorzystanie systemowych potoków, a właściwie urządzenia tego typu[8].
Pokażę teraz jak przygotować dane pobierane przez program. Po pierwsze musimy utworzyć urządzenie potoku /dev/ips (jako root):
mkfifo /dev/ips
oraz na wszelki wypadek nadać mu prawa:
chmod 777 /dev/ips
Kolejną czynnością jest konfiguracja demona rsyslog[9] (bo taki jest obecnie w Debian Lenny) w taki sposób aby wysyłał również (nie tylko) wybrane logi do naszego potoku. W tym celu dodajemy do pliku /etc/rsyslog.conf linię:
kern.*,auth,authpriv.* |/dev/ips
i restartujemy demona:
/etc/init.d/rsyslog restart
Jako ciekawostkę podam informację, iż ten demon logowania oprócz przekazywania danych do potoku obsługuje również gniazda TCP. W systemie lokalnym nie będzie to lepsze rozwiązania ale stwarza to możliwości uruchomienia takiego skryptu również na maszynie zdalnej.
Rsyslog będzie teraz wysyłał do naszego potoku /dev/ips to samo co do plików:
- /var/log/kern.log - logi generowane przez jądro, głównie z iptables
- /var/log/auth.log - logi generowane przez mechanizm PAM[10] oraz mechanizmy wewnętrznej autoryzacji demonów np. sshd[11]
Dodam do zapory regułę, która będzie logować każdy ruch kierowany z sieci prywatnej do portu 80/TCP:
iptables -A FORWARD -s 192.168.44.0/24 -p tcp --dport 80 --syn -m state --state NEW -j LOG --log-prefix "dport 80 " --log-tcp-options --log-ip-options --log-tcp-sequence
Czyli nowe połączenia TCP kierowane na port 80/TCP z sieci 1921.68.44.0/24 zostaną zalogowane z prefiksem "dport 80 ". Prefiks posłuży do późniejszej identyfikacji dla systemu IPS.
Pamiętaj, że reguła i łańcuch jest dowolny. Ja wstawiam taką przed regułą przepuszczającą ruch na port 80/TCP aby szybko otrzymać przykładowe wyniki. Natomiast system IPS może zareagować na dowolną regułę, bardziej użyteczną, ale to już inny temat.
Używając polecenia powłoki cat spróbujemy pobrać zawartość potoku (aby zakończyć wciśnij Ctrl+C):
cat /dev/ips
Teraz z jednego z hostów z sieci 192.168.44.0/24 musimy uruchomić przeglądarkę internetową i załadować kilka storn WWW. W moim przypadku będzie to host o adresie 192.168.44.4.
brama:~# cat /dev/ips
Apr 4 18:15:28 brama kernel: [13780.386435] dport 80 IN=eth3 OUT=eth1 SRC=192.168.44.4 DST=209.85.129.138 LEN=48 TOS=0x00 PREC=0x00 TTL=127 ID=2101 DF PROTO=TCP SPT=49289 DPT=80 SEQ=2653279655 ACK=0 WINDOW=8192 RES=0x00 SYN URGP=0 OPT (020405B401010402)
Apr 4 18:15:28 brama kernel: [13780.490090] dport 80 IN=eth3 OUT=eth1 SRC=192.168.44.4 DST=74.125.170.161 LEN=48 TOS=0x00 PREC=0x00 TTL=127 ID=2105 DF PROTO=TCP SPT=49290 DPT=80 SEQ=147814656 ACK=0 WINDOW=8192 RES=0x00 SYN URGP=0 OPT (020405B401010402)
Apr 4 18:17:01 brama CRON[2544]: pam_unix(cron:session): session opened for user root by (uid=0)
Apr 4 18:17:01 brama CRON[2544]: pam_unix(cron:session): session closed for user root
Miałem szczęście, bo oprócz logów z iptables pojawiły się logi z demona cron[12], które pochodzą z pliku /var/log/auth.log. Sprawdzanie etapuinput mojego IPS-a mogę uznać za zakończone.
Czas uchwycić jakieś niepożądane zdarzenie. Najpierw musimy wprowadzić regułę do pliku rule.ips, a jeszcze wcześniej zapoznać się z formatem reguł. Opisze go na dwóch przykładach. Pierwszy to już przedstawione logi pochodzące z autoryzacji serwera SMTP, a drugi to zdarzenie, na którym przetestujemy nasz system IPS.
Mar 30 05:19:01 prv dovecot-auth: pam_unix(dovecot:auth): check pass; user unknown
Mar 30 05:19:01 prv dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=admin@ rhost=202.55.172.6
Podczas każdego nieprawidłowego logowania do serwera SMTP generowane są dwie przykładowe linie. Druga zawiera szczegółowe dane więc na jej podstawie zbudujemy regułę IPS.
Ogólny format reguł ma postać:
name;;find1,find2,NULL;;key1,between1,between2;key2,between1,between2;;iptables rule param param;1,2;N;time
Reguła dzieli się na cztery sekcje oddzielone znakami ";;":
- Pierwsza sekcja to name - jest to po prostu nazwa reguły, coś na wzór reguł systemu Snort[13].
- Druga sekcja zawiera trzy pola oddzielone znakiem "," - jest to wymóg. Są to trzy frazy, po których system IPS pozna szukane logi. Jeżeli nie używamy wszystkich, należy wstawić wartość "NULL" - to również jest wymóg.
- Trzecia sekcja to tzw. klucze/wartości, które chcemy z logów pozyskać na potrzeby kolejnej sekcji - reguł iptables. W przeciwieństwie do poprzedniej sekcji, gdzie ściśle było określone ile ma być wartości ta jest elastyczna. Może ich być jedna lub dziesięć. Każda wartość ma postać klucz=wartość i w regule IPS składa się z trzech definicji oddzielonych przecinkiem ",". Klucze natomiast oddziela się znakiem ";". Aby np. z przykładowego logu wydobyć wartość klucza rhost musimy napisać ";;rhost,=, ;;", ponieważ wartość tego klucza znajduje się po nazwie klucza (zawsze tak musi być) i znajduje się między elementami "=" i " " (w logach na końcu rzeczywiście jest spacja). Aby z przykładowego logu wydobyć jeszcze wartość użytkownika admin "ruser=admin@" należało by dodać definicje kolejnego klucza do sekcji ";;rhost,=, ;ruser,=,@;;". Należy pamiętać, że wartości kluczy mogą być użyte tylko do definicji regułiptables!
- Czwarta sekcja składa się z czterech części. Pierwsza to definicja reguły iptables, która ma zostać wykonana w odpowiedzi na zaistniałe logi/zdarzenie. W regule tej znajdują się magiczne słowa "param", które będą zastępowane wartościami wybranych kluczy. Można używać jednego klucza wielokrotnie w tej samej regule. Klucze są wybierane w kolejności wskazanej przez drugą cześć. Jeżeli widnieją tam wartości oddzielone przecinkiem "1,2" to znaczy: zastąp pierwsze (od lewej oczywiście) wystąpienie słowa "param" w regule wartością klucza pierwszego, a drugie wystąpienie wartością klucza drugiego. Aby dwa razy użyć tego samego IP, np. wartości klucza pierwszego wpisujemy "1,1". Tu należą się pewne wyjaśnienia. Z racji tego, że cały mechanizm liczy ile danych definicji reguł dopasował dla każdego adresu IP indywidualnie wymagane jest, aby wartością klucza pierwszego był ten adres IP! Trzecia część określa ile razy dane zdarzenie może wystąpić zanim zostanie wykonana reguła. Czwarta część określa w jakim przedziale czasowym ma wystąpić to N-krotne dopasowanie reguły zanim system IPS zareaguje. Przedział ma formę hh:mm:ss.
Teraz napiszmy regułę dla przykładu pierwszego. Będzie miała ona postać:
pop3 auth;;dovecot-auth,authentication failure,NULL;;rhost,=, ;;iptables -I INPUT 3 -s param -j DROP;1;3;00:01:00
Zwróć uwagę, że między znakami oddzielającymi sekcje oraz poszczególne części czy wartości nie może być przypadkowych odstępów. W sekcji kluczy spacja również jest znakiem wskazującym.
Powyższa reguła oznacza:
- Reguła nazywa się "pop3 auth" - tak będzie podpisywana w logach.
- Log wskazujący na szukane zdarzenie będziemy identyfikować za pomocą dwóch warunków; muszą się w nim znajdować frazy "dovecot-auth" oraz "authentication failure". Z trzeciego zrezygnowaliśmy więc należało wstawić "NULL".
- Z logu wydobywamy jedną wartość opisaną kluczem "rhost,=, " dla ciągu znaków "rhost=202.55.172.6 " (klucz rhost będzie miał wartość 202.55.172.6) - pamiętaj, że pierwszy klucz musi być adresem IP!
- Dla zdarzenia wstawiamy regułę "iptables -I INPUT 3 -s param -j DROP".
- Dla pierwszego wystąpienia słowa param w regule przypisz wartość klucza nr 1 "1" (reguła zyska postać: iptables -I INPUT 3 -s 202.55.172.6 -j DROP).
- Zdarzenie ma wystąpić "3" trzy razy.
- Powyższe zdarzenia muszą wystąpić w ciągu 1 minuty "00:01:00".
Mówiąc krótko: jeżeli w ciągu 1 minuty zostanie wygenerowany trzykrotnie przez ten sam adres IP log typu:
Mar 30 05:19:01 prv dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=admin@ rhost=202.55.172.6
system IPS doda do zapory w łańcuchu INPUT na trzeciej pozycji regułę:
iptables -I INPUT 3 -s 202.55.172.6 -j DROP
Ale to nie wszystko. Zostanie wygenerowany jeszcze odpowiedni log do pliku ips_activate.log, Do pliku ipt_rule.sh zostanie dodana reguła do skryptu wprowadzonych reguł iptables przez system IPS. Dzięki temu nie tracimy wprowadzonych reguł po restarcie zapory ogniowej.
Dlaczego w tym przykładzie reguła zapory została wstawiona jako trzecia w łańcuchu INPUT? Ponieważ wcześniej znajdują się inne ważne reguły. Zastanów się co by się stało jeżeli z sfałszowanego adresu IP ktoś by zaatakował Twój system IPS, a tym adresem by był akurat adres Twojego serwera DNS. Jest to zagadnienie dłuższej treści ponieważ my raczej szukamy wielokrotności, a sesje TCP nie da się sfałszować. Nie zmienia to faktu, że system ten powinien mieć jeszcze zaimplementowany mechanizm tzw. białej listy adresów IP.
Czas przetestować system IPS. Zrobimy to na przykładzie nie wymagającym logowania na pocztę (ponieważ takiej nie mam na systemie testowym) lecz na przykładzie logowania się do serwera SSH pracującego na systemie testowym.
Gdy podczas logowania wpisuje się złe hasło za każdym razem generowany będzie log:
Apr 4 18:03:50 brama sshd[2528]: Failed password for root from 192.168.44.4 port 49285 ssh2
Możemy na jego podstawie stworzyć regułę. Zawartość pliku rule.ips:
# Apr 4 18:03:50 brama sshd[2528]: Failed password for root from 192.168.44.4 port 49285 ssh2
auth sshd;;sshd,Failed password for root,NULL;;from, , ;;iptables -I INPUT 1 -s param -j DROP;1;3;00:01:00
Dobrą praktyką jest umieszczanie w nim również logów, na które ma reagować. Nasz IPS linie zaczynające się od znaku "#" traktuje jako komentarze, więc możemy bez problemu wklejać przykłady logów.
Więc nasza reguła nazywa się "auth sshd". Poznamy ją po frazach "sshd" oraz "Failed password for root" (dotyczy tylko użytkownika root). Pobieramy adres źródłowy z klucza from "from, , ", który znajduje się między spacjami. System IPS w odpowiedzi wstawi do zapory regułę "iptables -I INPUT 1 -s param -j DROP" i zastąpi słowo kluczowe "param" wartością klucza pierwszego "1" 192.168.44.4. Zdarzenie powinno wystąpić trzy razy "3" w ciągu minuty "00:01:00".
Skrypt możesz pobrać ze strony projektu: http://code.google.com/p/ipsoutline/
Plik ips_outline.py możesz rozpakować do katalogu /root (choć uruchamianie skryptów z poziomu użytkownika root jest niezalecane). W katalogu tym oprócz pliku rule.ips z regułami powinien znajdować się jeszcze pliki ipt_rule.sh dla reguł iptables oraz plik ips_activate.log do logowania poczynań systemu IPS. Oba ostatnie pliki należy przed uruchomieniem skryptu utworzyć samemu. Gdy będziemy mieli już komplet plików możemy uruchomić skrypt, pokaże on następujące komunikaty:
brama:~# ./ips_outline.py
IPS(Interactive firewall) Grzegorz Kuczyński
Read rule.ips...
auth sshd;;sshd,Failed password for root,NULL;;from, , ;;iptables -I INPUT 1 -s param -j DROP;1;3;00:01:00
Read ipt_rule.sh exist iptables rule...
Read /dev/ips pipe...
^C
Quit in Ctrl-C
brama:~#
Skrypt nie posiada żadnej obsługi błędów więc wszystko musi się zgadzać. Jako pierwszy zostanie przeczytany plik z regułami IPS, skrypt wyświetla istniejące reguły. Podczas czytania ipt_rule.sh nie znalazł żadnych reguł ponieważ jest to jego pierwsze uruchomienie. Skrypt czyta je aby wprowadzić do swoich rejestrów wykaz adresów IP, które już w przeszłości zostały dodane. W ten sposób zabezpiecza się przed duplikowaniem wprowadzonych reguł.
Każde dodanie nowej reguły oraz stwierdzenie, iż określone zdarzenie ponownie wystąpiło a reguła już istnieje zostanie wyświetlone i zalogowane.
Aby zakończyć działanie skryptu w normalnym trybie należy użyć kombinacji Ctrl+C.
Możemy teraz uruchomić skrypt i przyglądać się komunikatom pokazywanym poniżej linii "Read /dev/ips pipe...". Z innego systemu spróbujemy zalogować się na użytkownika root podając trzy razy błędne hasło w ciągu jednej minuty (upewnij się czy zapora jest dobrze ustawiona). Powinniśmy ujrzeć komunikaty podobne do tych:
** IPS 2010.4.4 22:42:47 auth sshd: iptables -I INPUT 1 -s 192.168.44.4 -j DROP n=3 ip=192.168.44.4 [timeline: 0:0:17 in time 00:01:00]
Add rule in ipt_rule.sh
Sprawdźmy jeszcze co pojawiło się w odpowiednich plikach:
/var/log/auth.log:
Apr 4 22:42:30 brama sshd[2654]: Failed password for root from 192.168.44.4 port 49341 ssh2
Apr 4 22:42:34 brama sshd[2654]: Failed password for root from 192.168.44.4 port 49341 ssh2
Apr 4 22:42:47 brama sshd[2654]: Failed password for root from 192.168.44.4 port 49341 ssh2
ipt_rule.sh (co zostanie pokazane przy następnym uruchomieniu skryptu):
iptables -I INPUT 1 -s 192.168.44.4 -j DROP
ips_activate.log:
IPS 2010.4.4 22:42:47 auth sshd: iptables -I INPUT 1 -s 192.168.44.4 -j DROP n=3 ip=192.168.44.4 [timeline: 0:0:17 in time 00:01:00]
Zwróć uwagę na parametr "timeline" z logów skryptu. Wskazuje on na 17 sekund, a samo wykonanie reguły nastąpiło w tej samej sekundzie, w której miało miejsce ostatnie wystąpienie logu. Od pierwszego zdarzenia do trzeciego upłynęło 17 sekund, co można wyczytać z logów.
Do zapory została dodana reguła w łańcuchu INPUT na samym początku, sprawdź:
brama:~# iptables -L -nv Chain INPUT (policy ACCEPT 260 packets, 20840 bytes) pkts bytes target prot opt in out source destination 15 1794 DROP all -- * * 192.168.44.4 0.0.0.0/0Możesz jeszcze sprawdzić czy możliwe jest połączenie się z hosta o adresie 192.168.44.4.
Ponownie uruchamiając skrypt zauważymy:
brama:~# ./ips_outline.py
IPS(Interactive firewall) Grzegorz Kuczyński
Read rule.ips...
auth sshd;;sshd,Failed password for root,NULL;;from, , ;;iptables -I INPUT 1 -s param -j DROP;1;3;00:01:00
Read ipt_rule.sh exist iptables rule...
iptables -I INPUT 1 -s 192.168.44.4 -j DROP
Read /dev/ips pipe...
Tym razem w pliku ipt_rule.sh znajdują się reguły. Ma to znaczenie w następującej sytuacji. Gdy zresetujemy zaporę ponownie będziemy mogli się zalogować z hosta 192.168.44.4. Mając uruchomiony system IPS, ponownie spróbujmy się trzykrotnie zalogować na użytkownika root wpisując złe hasło. Tym razem otrzymamy komunikat:
IPS 2010.4.5 0:27:45 Rule exist: iptables -I INPUT 1 -s 192.168.44.4 -j DROP [n=3 ip=192.168.44.4 timeline: 0:0:20 in time 00:01:00]
Również taki sam wpis zostanie dodany do pliku ips_activate.log, a reguła się nie wykona. Połączenie z hostem 192.168.44.4 nadal jest możliwe!
Dzieje się tak dlatego, że idea była taka aby plik ipt_rule.sh dołączać do zapory, również przed innymi regułami. Jeżeli plik ten byłby wywołany podczas restartu zapory system IPS nie dodał by tego adresu IP ponownie do reguł a reguła nadal by obowiązywała. Nawet teraz możemy naprawić nasz błąd wywołując ręcznie plik ipt_rule.sh. Nie zapomnij ustawić odpowiednie prawa do pliku:
chmod +x ipt_rule.sh
./ipt_rule.sh
Do omówienia z grubsza pozostały jeszcze dwa zagadnienia. Pierwsze to w jaki sposób zatrzymać skrypt jeżeli zostanie uruchomiony w tle:
./ips_outline.py &
Należy wysłać do potoku napis "stop":
brama:~# echo stop >> /dev/ips
Stop signal in /dev/ips
[1]+ Done ./ips_outline.py
Aby dodać skrypt do usług uruchamianych przez system możemy dodać następujący wpis do pliku /etc/rc.local:
/root/ips_outline.py > /dev/null &
następnie uruchomić wpisane tam usługi:
/etc/init.d/rc.local start
Zasadniczo celem tego skryptu było jak najszybsze (zaraz po identyfikacji incydentu) podjęcie stosownych działań modyfikując konfiguracje zapory ogniowej. Bez wątpienia ten skrypt spełnia tę funkcje w najwyższym z możliwych poziomów (przynajmniej jeżeli chodzi o czas reakcji). Mechanizm kluczy i wartości był konstruowany po analizie logów generowanych przez iptables oraz system i różne usługi. Jego głównym celem jest pobieranie danych do regułiptables, więc zazwyczaj będą to adresy IP, porty, ale nie tylko. Reguły iptables oferują bardzo dużo możliwości, może to być zablokowanie intruza tylko na jakiś czas.
Wariancji dotyczących wykorzystania takiego skryptu, jak i jego samej postaci może być bardzo dużo. Zapewne ciekawe rozwiązania będę prezentował w kolejnych wpisach na swoim blogu.
No comments:
Post a Comment