Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Python w służbie pentestera
Wstęp
Z tych powodów jest także jednym z częściej wybieranych przez specjalistów od bezpieczeństwa języków służących do pisania narzędzi przydatnych w tej dziedzinie.Oto kilka najbardziej znanych.
1. scapy
Na pewno każdy, kto zetknął się z tematyką szeroko rozumianego bezpieczeństwa informatycznego, słyszał bądź używał świetnego generatora pakietów scapy. Scapy napisany został w Pythonie, każdy, kto ma ochotę przeanalizować jego kod źródłowy, może to zrobić, zaglądając do oficjalnego repozytorium kodu na stronie, bądź pobrać go bezpośrednio na swój komputer (potrzebny będzie zainstalowany klient systemu kontroli wersji Mercurial).
2. sqlmap
Drugim powszechnie znanym narzędziem stworzonym przy pomocy Pythona jest sqlmap, czyli zautomatyzowany skaner podatności SQL Injection. Także w jego przypadku autorzy stawiają na otwartość kodu źródłowego, więc nie ma żadnych przeszkód, by repozytorium kodu sqlmapa pobrać i zapoznać się z nim lokalnie (wymagany klient systemu kontroli wersji Git).
3. w3af
Kolejnym przykładem jest framework do przeprowadzania testów penetracyjnych aplikacji webowych – w3af. Ten rozwijany przez Andresa Ranchio projekt to doskonały przykład na to, jak Python może posłużyć do zbudowania zaawansowanej aplikacji, której możliwości nie ograniczają się jedynie do wyświetlania prostych komunikatów na ekranie (w3af można używać zarówno jako aplikacji konsolowej, jak i desktopowej z graficznym GUI). Kod źródłowy dostępny jest w serwisie GitHub – andresriancho/w3af.
4. Golismero
Projektem podobnym do w3af, choć nieco mniej rozbudowanym, jest Golismero – bardzo przyjemne w użyciu zautomatyzowane narzędzie do wyszukiwania podatności w aplikacjach webowych. Golismero wyróżnia się funkcją generowania raportu w formacie HTML lub tekstowym (co może przydać się np. przy przygotowywaniu raportu z testu penetracyjnego) oraz łatwością rozbudowy o nowe pluginy. Kod źródłowy Golismero można pobrać z repozytorium w serwisie GitHub – golismero/golismero.
To tylko kilka przykładów z bardzo bogatej gamy programów napisanych w Pythonie. Ale to nie koniec: jest to też jeden z najczęściej wykorzystywanych języków do tworzenia exploitów czy wszelkiej maści rozszerzeń do innych popularnych narzędzi – np. dla Burp Suite.
Poznaliśmy kilka projektów stworzonych w Pythonie, czas przyjrzeć się nieco dokładniej samemu językowi i paru jego funkcjom.
Dlaczego warto poznać Pythona
Niewątpliwie jedną z bardziej pożądanych umiejętności każdego specjalisty bezpieczeństwa IT jest zdolność programowania (choćby podstawowa) i znajomość co najmniej jednego języka programowania.
Do efektywnej pracy (przykładowo w charakterze specjalisty od bezpieczeństwa aplikacji webowych) przydaje się znajomość HTML, JavaScript oraz technologii serwerowych, takich jak PHP, Java/JavaEE, Perl, ASP.NET. Specjalista zajmujący się analizą złośliwego oprogramowania (wirusów, rootkitów, malware instalującego się w systemie operacyjnym) powinien mieć opanowane w stopniu co najmniej dobrym takie języki jak C, C++ czy Assembler. Każdy, w zależności od swoich upodobań oraz przyjętej ścieżki rozwoju zawodowego i w ramach specjalizacji, powinien we własnym zakresie poświęcić nieco czasu na poznanie i naukę wybranego bądź wybranych języków.
Wydaje się jednak niezbędnym minimum, by każdy, niezależnie od konkretnego zadania, któremu poświęca swój czas, był w stanie pomóc sobie przez zautomatyzowanie pewnych czynności w postaci choćby prostego skryptu powłoki czy konsolowej aplikacji napisanej w interpretowanym języku (Perl bądź Python).
Ma to dwojaką korzyść – nauka języka oraz jego stosowanie w praktyce bardzo rozwijają warsztat oraz umiejętności praktyczne, co jest cechą szczególnie pożądaną w branży Security. Drugą korzyścią jest budowanie własnego, unikalnego zestawu narzędzi, które bardzo często, jako wyspecjalizowane w konkretnym zadaniu (niemal jak skalpel chirurgiczny), sprawdzą się dużo lepiej niż dostępne powszechnie gotowe rozwiązania-kombajny.
Wybrałem Pythona między innymi z powodów, które podałem na wstępie – jego względnie dużą popularność w porównaniu choćby do Perla, dostępność na różnych platformach oraz całkiem prostą do przyswojenia składnię – choć tu początkowo wpadłem w pewną pułapkę, wynikającą z doświadczenia z innymi językami – bloki kodu w Pythonie są wyznaczane przez głębokość wcięć, a nie nawiasy klamrowe, jak ma to miejsce w C, Javie, PHP czy JavaScript.
Trochę podstaw
Programy w Pythonie bardzo rzadko pisane są z wykorzystaniem jedynie podstawowych konstrukcji języka. Do budowy bardziej skomplikowanych skryptów wykorzystuje się moduły, czyli gotowe do użycia biblioteki wyspecjalizowane w określonych zadaniach.
W przykładach zostały użyte moduły:
- httplib – będący implementacją protokołu HTTP po stronie klienta,
- sys – zawierający mnóstwo wbudowanych funkcji i pozwalający m.in. na odczyt argumentów linii poleceń,
- os – będący interfejsem umożliwiającym wykonywanie poleceń powłoki systemowej.
Poza modułami dostępnymi wraz z językiem, Python umożliwia programiście pisanie własnych i ich wykorzystywanie w innych programach.
Skrypt pierwszy – Banner Grabber
Na początek coś bardzo prostego – Banner Grabber, czyli narzędzie do odpytywania serwera WWW o jego sygnaturę. Nazwa skryptu pochodzi od techniki znanej jako „banner grabbibng”.
Kod źródłowy skryptu zawiera kilka elementów, które poniżej omawiam dokładniej:
#!/usr/bin/python # [1] import httplib # [2] import sys def main(host, port, url): # [6] conn = httplib.HTTPConnection(host, port); # [7] conn.request("HEAD", url) # [8] response = conn.getresponse() # [9] print "HTTP response code: ", response.status # [10] print "web server banner: ", response.getheader("Server") if __name__ == '__main__': # [3] host = sys.argv[1] # [4] port = sys.argv[2] url = sys.argv[3] main(host, port, url) # [5]
W linijce oznaczonej [1] znajduje się informacja, że skrypt jest napisany w języku Python i do jego uruchomienia system powinien użyć interpretera tego języka (a konkretnie – uruchomić program python z katalogu /usr/bin).
Kolejne dwie instrukcje [2] służą do zaimportowania do naszego skryptu modułów httplib
oraz sys
– będziemy korzystać z ich funkcjonalności w dalszej części skryptu.
Konstrukcja if __name__ == '__main__':
w linii [3] jest pewnym programistycznym trickiem charakterystycznym dla Pythona, dokładniejszy opis tej techniki można znaleźć np. pod tym adresem Użycie __name__ w Pythonie).
W kolejnych linijkach [4] odczytujemy argumenty wiersza poleceń. sys.argv
to tablica, której pierwszy element (o zerowym indeksie – w Pythonie, podobnie jak w innych językach, indeksy elementów tablic liczymy od 0) – to nazwa skryptu. Dlatego argumenty, którymi w tym przypadku będą: host, port oraz adres url, odczytujemy jako elementy tablicy argv
o indeksach od 1 do 3. Przykładowe wywołanie (odpytanie serwera WWW serwisu sekurak.pl, na porcie 80 i w głównym drzewie katalogów /) będzie wyglądało następująco:
$ ./banner_grabber.py sekurak.pl 80 /
[5] to wywołanie funkcji, zdefiniowanej w [6]. Jak widzimy, funkcja przyjmuje jako argumenty to, co wcześniej odczytaliśmy z linii poleceń. Następnie [7] tworzone jest połączenie do określonego hosta na podanym porcie, a następnie wywoływany jest adres url [8]. Stosujemy tu metodę HEAD, gdyż nie zależy nam na treści strony, a jedynie na samym nagłówku odpowiedzi HTTP. Ze zwróconego nagłówka [9] odczytujemy liczbowy status odpowiedzi HTTP (tak dla pewności) [10] oraz interesującą nas informację, czyli nagłówek Server.
W odpowiedzi powinniśmy otrzymać komunikat:
HTTP response code: 301 web server banner: Apache
Skrypt drugi – Reverse DNS Scanner
Bardzo często w trakcie przeprowadzania rekonesansu zachodzi konieczność ustalenia, jakie nazwy domenowe odpowiadają konkretnym adresom IP w badanej sieci. Jak wiemy, służy do tego m.in. konsolowa komenda host
(aby więcej dowiedzieć się o opcjach udostępnianych przez tę komendę, wpisz w swojej konsoli polecenie man host
).
Jeśli zachodzi konieczność sprawdzenia kilku adresów IP, wydanie za każdym razem polecenia host
wraz z adresem IP nie wydaje się jeszcze jakoś specjalnie uciążliwe. Problem pojawia się w przypadku większej liczby adresów IP, np. całej podsieci składającej się z 255 adresów.
W takim wypadku można skorzystać z drugiego skryptu, a mianowicie prostego DNS Reverse Scannera. Jedynym jego zadaniem jest przeiterowanie przez cały zakres podanych mu adresów IP i wykonanie polecenia host
na każdym z nich.
#!/usr/bin/python import sys, os def main(ip_start, ip_end): start = ip_start.split(".") # [1] end = ip_end.split(".") for lastGroup in range( int(start[3]), int(end[3]) ): ip = "%s.%s.%s.%s" % (start[0],start[1],start[2],lastGroup) os.system('host %s' % (ip)) # [2] # print start, end if __name__ == '__main__': main(sys.argv[1], sys.argv[2])
Poza konstrukcją [1], która rozbija dwa podane adresy IP, budując z ostatnich części adresów przedział, w jakim ma wykonać się pętla poniżej, warta zwrócenia uwagi jest linijka [2]. Polecenie system
z modułu os
pozwala nam na wykonanie dowolnego polecenia powłoki tak, jakbyśmy wpisali je bezpośrednio w konsoli (wynik otrzymujemy również w konsoli).
Jak można się spodziewać, skrypt zwraca listę informacji o odnalezionych nazwach domenowych z serwerów DNS, odpowiadających badanym adresom IP:
$ ./reversedns.py 212.77.100.10 212.77.100.20 10.100.77.212.in-addr.arpa domain name pointer www.rozrywkaprzyszlosci.orange.pl. 11.100.77.212.in-addr.arpa domain name pointer centralagier.pl. 12.100.77.212.in-addr.arpa domain name pointer si.wp.pl. 13.100.77.212.in-addr.arpa domain name pointer komiks.wp.pl. 14.100.77.212.in-addr.arpa domain name pointer telefon.wp.pl. 15.100.77.212.in-addr.arpa domain name pointer profilbiznesowy.wp.pl. 16.100.77.212.in-addr.arpa domain name pointer horoskop.wp.pl. 17.100.77.212.in-addr.arpa domain name pointer adtotal.pl. 18.100.77.212.in-addr.arpa domain name pointer w.wp.pl. 19.100.77.212.in-addr.arpa domain name pointer historia.wp.pl.
Dla porównania – podobne narzędzie napisałem kiedyś w języku Perl. Jego źródło można znaleźć pod tym adresem. Skrypt w Pythonie jest nieco bardziej przejrzysty i łatwiejszy w interpretacji.
Skrypt trzeci – Admin Panel Scanner
W trakcie testów penetracyjnych aplikacji webowych jedną z pierwszych czynności jest próba odnalezienia bądź odgadnięcia lokalizacji części administracyjnej (np. w celu zbadania podatności na błędy uwierzytelniania). Ponieważ programiści to ludzie z natury leniwi, w większości przypadków lokalizacja takiego panelu administracyjnego to katalog lub plik o nazwie wywodzącej się od słów 'admin’ lub 'login’.
Ponieważ konkretna lokalizacja może zależeć od technologii, w jakiej powstał serwis, bądź od struktury katalogów wymuszonej np. przez użycie określonego frameworka czy systemu, do szybkiej lokalizacji sekcji administracyjnej może przydać się kolejny z prezentowanych skryptów:
#!/usr/bin/python import httplib import catalogs # [1] import sys def scan(conn, path, dirs): logfile = open("logfile.txt", "a") # [3] for d in dirs: d = d.replace("\n", "") _url = "/%s/%s" % (path,d) # [4] _output = "" conn.request("GET", _url) response = conn.getresponse() response.read() # [5] if (response.status == 200 or response.status == 302 or response.status == 304): _output = "HTTP %s %s \t\t\turl is %s" % (response.status, response.reason, _url) if (response.status == 401): _output = "HTTP %s %s \t\t\t, Unauthorized; url is %s" % (response.status, response.reason, _url) if (response.status == 403): _output = "HTTP %s %s \t\t\t, Needs authorization; url is %s" % (response.status, response.reason, _url) if (_output): # [6] print _output logfile.writelines(_output + "\n") # [7] logfile.close() # [8] def main(): adminpanels = catalogs.adminpanels # [2] conn = httplib.HTTPConnection(sys.argv[1]) path = sys.argv[2] print "Trying to locate administration panel:" scan(conn, path, adminpanels) if __name__ == '__main__': main()
Podobnie jak w pierwszym skrypcie skorzystamy z modułów sys
oraz httplib
. Dodatkowo w naszym programie użyjemy modułu naszego autorstwa. Moduł ten będzie składał się tylko z definicji tablicy zawierającej listę ścieżek do kilkuset najpopularniejszych lokalizacji paneli administracyjnych. Moduł można pobrać spod tego adresu, a używamy go w postaci prostego przypisania do zmiennej w naszym skrypcie [2].
Po wywołaniu funkcji scan()
wraz z interesującymi nas parametrami, czyli odczytanymi z linii poleceń nazwami hosta i ścieżki do aplikacji www na serwerze, pierwszą instrukcją jest otwarcie pliku logu [3], w którym skrypt zapisze wszystkie odnalezione ścieżki. Jak widać, otwarcie pliku w Pythonie graniczna się do podania jego nazwy oraz trybu (zapis, odczyt, dodawanie) – więcej na ten temat znajduje się w dokumentacji. W [4] następuje złożenie konkretnego adresu url, składającego się z podanej jako argument ścieżki do aplikacji i doklejonej do niej lokalizacji odczytanej z tablicy adminpanels
.
Wszystko odbywa się w pętli, za każdym razem po wywołaniu adresu sprawdzany jest status HTTP odpowiedzi [5]. Jeśli zawiera on wartość wskazującą, że pod danym adresem znajduje się dostępny zasób (200, 301, 302, 304, 401 lub 403), skrypt wyświetla nam odpowiednią informację na wyjściu [6], jednocześnie zapisując ją w pliku logu [7].
Po zakończeniu pracy plik z logiem jest zamykany, by zwolnić zasoby systemowe [8], a program kończy działanie. Wywołując Admin Panel Scanner przykładowo dla serwisu opartego na WordPress, otrzymamy:
$ ./adminscanner.py localhost wordpress Trying to locate administration panel: HTTP 200 OK url is /wordpress/wp-login.php $ cat logfile.txt HTTP 200 OK url is /wordpress/wp-login.php $
Skrypt czwarty – XSS UTF-16 Payload Encoder
Na zakończenie skrypt, którego jedynym zadaniem jest translacja podanego ciągu do sekwencji znaków zakodowanych w postaci wartości UTF-16. UTF-16 to jeden z akceptowanych przez przeglądarki internetowe formatów zapisu kodu JavaScript. Kodowanie w formacie UTF-16 jest często wykorzystywane w przypadku tworzenia payloadów do testowania podatności na błędy XSS (innymi formatami jest np. URL Encoding, który zamienia znaki specjalne, jak < czy ” na %3c i %22).
#!/usr/bin/python # utf16encoder import sys def encode(str): encoded = '' for character in str: encoded += "\u" + hex(ord(character)).replace("0x","00") # [1] print encoded def usage(): # [2] print "Too few arguments.\n" print "usage: utf16encoder.py \n" quit() if __name__ == '__main__': if sys.argv.__len__() == 2: encode(sys.argv[1]) else: usage()
W porównaniu z poprzednimi skryptami ten wzbogaciliśmy o niewielką pomoc w przypadku, gdy użytkownik poda nieprawidłową liczbę argumentów [2]. Omówienie należy się też linijce [1] – iterując po kolejnych znakach ciągu wejściowego, zamieniamy każdy znak na (kolejno):
- jego odpowiednik dziesiętny w kodzie ASCII – realizuje to zadanie funkcja ord(),
- wartość otrzymaną jako rezultat f-cji ord() zamieniamy na odpowiadającą jej liczbę szesnastkową przy pomocy hex(),
- ponieważ poprzednia operacja dodaje nam ciąg '0x’ przed szesnastkową reprezentacją znaku, a my potrzebujemy postaci UTF-16, czyli cztery cyfry poprzedzone znakiem '\u’ wykonujemy kolejną zamianę, ostatecznie otrzymując odpowiednio zakodowany znak.
Po zakończeniu przeliczania wszystkich znaków ciągu zwracamy na wyjściu gotowy zakodowany payload:
$ ./utf16encoder.py "<script>alert('XSS')</script>" \u003c\u0073\u0063\u0072\u0069\u0070\u0074\u003e\u0061\u006c\u0065\u0072\u0074 \u0028\u0027\u0058\u0053\u0053\u0027\u0029\u003c\u002f\u0073\u0063\u0072\u0069 \u0070\u0074\u003e
Zakończenie
Przedstawione skrypty są bardzo proste, nie zawierają praktycznie żadnej obsługi błędów, a ich funkcjonalność pozostawia wiele do życzenia. Niemniej jednak mają jedną niezaprzeczalną zaletę: wykonują zadania, do których zostały napisane i robią to szybko niezależnie od środowiska, a ich uruchomienie sprowadza się do wpisania odpowiedniego polecenia w konsoli. Napisanie podobnego narzędzia, po nabraniu wprawy i doświadczenia w posługiwaniu się Pythonem, często może okazać się szybsze, niż wyszukanie, pobranie z sieci, instalacja i uruchomienie spełniającego konkretne zadanie „dużego” programu.
Niewątpliwie satysfakcjonująca będzie również świadomość, że korzystamy z własnoręcznie napisanego programu. Kto wie, być może z prostego skryptu, który powstanie z konieczności przeprowadzenia określonego zadania, z czasem powstanie rozbudowane i uznane w świecie specjalistów od zabezpieczeń narzędzie?
Źródła
Jak napisałem we wstępie, w Internecie istnieje bardzo dużo materiałów do samodzielnej nauki języka Python na każdym poziomie zaawansowania. Sam rozpocząłem swoją przygodę z tym językiem (do czego również zachęcam, ale może ktoś poda lepsze źródła w komentarzu do tego tekstu) od oficjalnego tutoriala dostępnego na stronie projektu pod adresem The Python Tutorial.
Oczywiście, jak w przypadku każdego języka, podstawą w trakcie nauki jest dokumentacja – na stronie Python v2.7.6 documentation znajdziemy podzieloną na działy, aktualizowaną i bardzo, bardzo bogatą dokumentację Pythona 2.7.6
Inne źródła:
- Python tools for penetration testers – spis narzędzi związanych z bezpieczeństwem, napisanych w Pythonie.
- Writing Basic Security Tools using Python – prezentacja Ali Al-Shemery (w formacie pdf) na opisywany temat i inspiracja do napisania powyższego tekstu. Stanowi doskonałe, szybkie wprowadzenie w tematykę programowania sieciowego w Pythonie, modułów httplib, urllib i ich wykorzystania. Zawiera też kilka praktycznych przykładów.
—Rafał 'bl4de’ Janicki– bloorq[at]gmail.com
Świetne!
Do komunikacji http polecam bibliotekę requests (http://requests.readthedocs.org). Ma o wiele wygodniejsze API niż domyślne biblioteki z Pythona.
Jedna z popularniejszych książek do nauki Pythona (darmowa!), przetłumaczona na polski: http://pl.wikibooks.org/wiki/Zanurkuj_w_Pythonie
@dex
Dzięki za linka. To opracowanie na Wikibooks bazuje właśnie na oficjalnym tutorialu ze strony python.org.
Dla kogoś, kto woli materiały w naszym rodzimym języku jest to doskonały zamiennik :)
Polecam książkę „Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers”, autorem jest TJ O’Connor.
Dzięki za informację :)
Faktycznie świetna pozycja, żałuję, że nie trafiłem na nią w trakcie pracy nad artykułem.
Swietny artykul, dzieki!
Dzięki :)
W „drodze” jest już druga część ;)
Ruby ;)
Bash ;) Swoją drogą widziałem kiedyś w dość starej książce o programowaniu w bashu, pełen kod sklepu interenetowego (CGI) :-)
Koleś który programuje w bashu powinien dostać Nobla, Guinessa i Oscara za samo zaparcie, motywacje oraz twórczość abstrakcyjną.
Masowy skaner LFI – nie skończony, trochę błędów, ale może się komuś będzie chciało go pociągnąć dalej ;P
http://pastebin.com/L5G405tG
+ w tym samym katalogu wrzucamy libkę google (http://pastebin.com/CdYQjS5V)
odpalamy, wpisujemy lfiscan, następnie 0, a później ile stron ma pobrać – np 100.
———————-
Automat do generowania CSRF PoC’y – jak ktoś nie ma licencji na burpa ;)
http://pastebin.com/Ff0cCWKx
Korzystając z okazji, wszystkich zainteresowanych Pythonem zachęcamy do odwiedzania serwisu polskiej społeczności Pythona, na którym to można znaleźć kursy, artykuły i aktualności dotyczące tego języka.
http://pl.python.org/
Dostępne jest także forum społeczności:
http://pl.python.org/forum/
… oraz kanał IRC (w sieci FreeNode):
#python.pl
Zapraszamy wszystkich czytelników Sekurat.pl!
Bardzo fajny artykuł, zwłaszcza, że przymierzam się do nauki języka. Mógłbyś napisać coś o wersji 2.7.6 vs 3.3? Dlaczego polecasz starszą?
@radq
Wybór wersji 2.7.6 był podyktowany czystym przypadkiem – zacząłem uczyć się tego języka właśnie z tutoriala poświęconego tej wersji i tak zostało :)
Na pewno dobrym pomysłem jest zapoznanie się z wersją 3, jednakże pomiędzy obiema wersjami występują pewne różnice, które sprawiają, że kod napisany w Pythonie 3 może nie zadziałać w interpreterze Pythona 2.
Dokładne różnice oraz ewentualne problemy z niezgodnością wersji zostały opisane w opracowaniu „Should I use Python 2 or Python 3 for my development activity?” na Wiki Pythona i myślę, że tam znajdziesz odpowiedź na swoje pytanie:
https://wiki.python.org/moin/Python2orPython3
A więcej konkretów na temat różnic można zobaczyć w
Python 3 Porting Guide:
http://docs.pythonsprints.com/python3_porting/py-porting.html
Porting to Python 3 – The Book Site –
Language differences and workarounds:
http://python3porting.com/differences.html
bl4de przyznaj się, że z lenistwa! uaktualnianie dokumentacji w głowie jest strasznie wyczerpujące ;P
Wersja 2.x istnieje dłużej więc dla niej jest więcej bibliotek, opisów, tutoriali. Ale 3.x nie zostaje w tyle więc warto znać obie wersje.
W razie pytań zapraszam na wspomniane forum pythona
http://pl.python.org/forum/
Bo wersji 3.x używają tylko autorzy tego języka ;)
https://python3wos.appspot.com/
czy ja wiem ;) nie jest tak tragicznie….
A taki Metasploit czy jigsaw są w Rubim. ;)
takie rzeczy akurat jeszcze łatwiej w bashu, w jednej linijce bez wielokilobajtowych bibliotek :-)
metasploita słyszałem przepisują na pythona właśnie, bo jak już zostało powiedziane, jest wszędzie