Sql injection jest jedną z najpopularniejszych i najczęściej wykorzystywanych technik mających na celu nieautoryzowane uzyskanie dostępu do bazy danych. Polega ona na takim manipulowaniu przekazywanymi danymi by ominąć filtrowanie i uzyskać dostęp do większej ilości danych niż było to pierwotnie zamierzone.
OK, tyle teorii – przejdźmy do praktyki.
Jako środowisko testowe wykorzystamy DVWA – w moim przypadku to kontener dockera postawiony poleceniem:
Jeśli jesteś już zapisany - Kliknij "Zaloguj się" i podaj swojego maila - treść zostanie odblokowana:
Podając dowolny id – np 1 pokażą nam się przykładowe dane:
Ok, zostawmy na chwilę formularz i spójrzmy w w źródło (“View Source”) – widzimy tam taki sposób wykonania zapytania:
Wystarczy więc wykorzystać operator łączenia zbiorów, czyli “union” by wydobyć potrzebne nam dane.
Podajemy jako id:
SQL Injection – poziom medium
W poziomie medium mamy trzy zmiany:
- Inaczej zapisane zapytanie
- Jako zabezpieczenie została dodana funkcja mysqli_real_escape_string
- Zamiast pola do wpisania wartości – rozwijana lista
Jak można je obejść
Punkt 1:
Nasze zapytanie ma teraz postać:
Obejście? Nie podajemy w wartości pojedyńczego cudzysłowia.
Punkt 2:
Wskazana funkcja w naszym przypadku nic nie przeszkadza gdyż nie jest poprawnie zaimplementowana
Punkt 3:
Musimy tutaj posłużyć się narzędziem które pozwala na przechwytywanie i zmianę parametrów dla wykonanego zapytania przed wysłaniem na serwer ( tj robi za proxy).
Narzędzie jest kilka, ja do tego celu wykorzystuje Burp Suite + przeglądarkę firefox.
Aby skonfigurować Burp Suite należy go pobrać, zainstalować, uruchomić ( domyślnie nasłuchuje na porcie 8080) i w przeglądarce uruchomić przesyłanie żądań przez Proxy.
W przypadku firefox-a ustawienie to znajduje się w Menu–.Preferences –>General i w dziale “Network Proxy” klikamy na “Settings…” i ustawiamy naszego burpa jako proxy:
W Burp Suite żądania możemy przechwytywać w ( kolejno od góry) Proxy–>Intercept – aby proxy przechwytywało żądania musi być uruchomiona opcja “Intercept is on”
Wracamy na chwilę do firefoxa, wybieramy dowolną wartość z listy i klikamy “Submit”
W burp nasze żądanie zostało przechwycone:
Przechodzimy do zakładki Params, gdzie mamy podane parametry, czyli nasze id, id sesji, poziom zabezpieczeń i wywołanie wysłania przyciskiem submit.
Modyfikujemy parametr id pamiętając o punkcie 1 i 2 – czyli wystarczy nie podawać pojedyńczego cudzysłowia na początku:
SQL Injection – Poziom Hard
Na poziomie hard nie mamy ani pola ani rozwijanej listy, a link do kliknięcia gdzie pojawia nam się nowe okno do wpisania id.
Różnica pomiędzy poprzednim poziomem jest taka, iż zmienna ta nie jest wysyłana jako parametr, a zmienna sesji ($_SESSION[‘id’]):
Format zapytania został przywrocóny z wersji low, czyli mamy zmienną w pojedyńczym apostrofie.
Wykorzystując zapytanie z poziomu low, oraz wiedzę na temat przechwycenia pakietu z poziomu medium przechwytujemy pakiet, pozmieniamy zmienną id na:
SQL Injection na przykładzie galerii zdjęć
Firma Acunetix udostępnia specjalnie przygotowaną stronę zawierającą wiele podatności.
Jej główne zadanie to pokazanie skuteczności ich produktu – ale skoro sami informują że jest otwarta dla wszystkich do testów to grzechem było by nie skorzystać.
Zajmijmy się więc galerią znajdującą się pod tym adresem:
Nas najbardziej interesuje to co w pasku adresu, a dokładniej “cat=1”.
Zmodyfikujmy ten parametr używając łączenia za pomocą znanego nam już union:
Otrzymaliśmy bardzo wyraźny komunikat:
Jeżeli w końcu “trafimy” z liczbą kolumn ( tutaj jest akurat 11) pojawi nam się spowrotem galeria:
Została ona jednak powiększona o jeden rekord – ten z naszego łączenia zbiorów, który widać na samym końcu:
Jak widać wyświetlają nam się liczby 7, 2 i 9 – zmodyfikujmy więc nasze zapytanie podstawiając pod nie użytkownika, nazwę bazy i wersję serwera mysql:
Więcej SQLi, czyli dodajemy and
Szukając podatnych aplikacji udało mi się trafić na taki oto projekt:
Jest tam 8 poziomów – od bardzo łatwego do experta. Zasady są proste – mamy wyświetlić jedynie wersję oraz użytkownika, oraz używać jedynie union.
Rozwiniemy też nasze zapytania o instrukcje warunkowe – do dzieła!
Level 1:
Podstawowy url to:
Autor ograniczył wyświetlanie wyników tylko do pierwszej wartości – w tym przypadku możemy ominąć to na dwa sposoby.
Pierwszym z nich jest użycie instrukcji warunkowej. Wyobraźmy sobie że zapytanie ma postać:
wtedy jego wynik będzie wynosił false, czyli nic się nie pojawi:
Jeżeli zaś do tego momentu zapytanie nie zwraca wartości dodajmy do niego drugą stronę która ma wartości i nam je zwróci:
Pisałem jeszcze o drugim sposobie – zasada jest ta sama co w pierwszym, tj lewa część musi nic nie zwracać.
Można to prosto osiągnąć podstawiając za id bardzo wysoką wartość której nie będzie w bazie.
W tym przypadku nie trzeba się wysilać i wystarczy jako id podać 2:
Level 2:
Sprawdzamy ilość kolumn plus umieszczamy zapytanie w pojedyńczym cudzysłowiu:
Level 3:
Tutaj zaczeły się schody – standardowe zapytania nie pokazywały nic , kombinacje pojedyńczych i podwójnych cudzysłowiów również.
Pomocny okazał się błąd składni – pokazał on ciekawy komunikat błędu:
Wystarczy więc zamiast union wpisać unionon , znaleść liczbę kolumn i gotowe:
Level 4:
Level 7:
Nad tym zadaniem spędziłem 3 godziny, i przyznam że gdyby nie to że wiedziałem że jest tam błąd nigdy bym go tam nie znalazł.
Większą część czasu robiłem ślepe próby myśląc że chodzi tutaj o konkretne id.
Po ponad godzinie zajrzałem do kodu źródłowego – a tam na początku ukryty obrazek:
Spojrzałem jeszcze raz – a tam coś ciekawego:
Kolejna godzina z próbami przesyłania różnych wartości z polem status nie przyniosły efektu – cały czas statusem było error.
Wróciłem do podstawowej wersji adresu – domyślna wartość to ok1!
Zacząłem od drugiej strony – podstawiałem zapytania union, ale nic nie zwracało. Zauważyłem jednak jedną prawidłowość – jeśli po prawej stronie podstawimy odpowiednią ilość wartości ukryte polez wróci “ok1” , jeśli złą – zwraca “error”
Skoro już wiedziałem że tabela ma 3 kolumny, podstawianie różnych wartości pod id nic nie daje to próbowałem kombinacji z “and” – okazało się to strzałem w dziesiątke!
Wysłanie takiego zapytania:
a odwrócenie user() z version() pokazuje użytkownika:
Level 8:
Pierwsze co zrobiłem to spojrzałem w źródło strony – nic ukrytego nie ma 🙂
Kolejna obserwacja – pod wartością 2 nic nie ma, więc nie trzeba instrukcji and.
Ale cokolwiek nie wpisałem to zawsze miałem komunikat “hacking attempt” – wygląda że wycina wszystko poza wartością.
Zacząłem kombinować więc z łączeniem zapytań plusami , spacjami czy innymi znakami opisanymi w kodowaniu znaków w adresach url – zadziałały dopiero tabulatory, ale nie do końca:
Mówi Wam to coś? Mi przypomniało “unionon”,więc odrazu go sprawdziłem:
Tutaj się trochę pobawiłem – koniec końców okazało się że jedno select musi być w drugim, tzn ciąg powinien wyglądać tak:
Level 9:
Na pierwszy ogien standardowe union:
Po zmniejszeniu liczb po prawej stronie było jeszcze ciekawiej:
Błąd fatal error czyta nam zmienną 1 z zapytania union!
Wystarczy teraz podmienić zmienną 1 na /etc/passwd ( dodając ją w podwójny cudzysłów) i cofać się do poziomu głównego systemu plików.
Zapytanie wygląda więc tak:
Level 10:
Na poziomie 10 mamy taki adres:
%3D to zakodowany znak równości, więc nasz ciąg wygląda tak:
Jest to nic innego jak zakodowany base64 – używając dekodera wynik to:
Ten wynik nic mi nie mówił – wrzuciłem go w rozmaite dekodery i w końcu trafiłem ( o zgrozo a tej samej stronie) na uudecoder, który jako wynik zwrócił mi 1 !
Dalej już z górki – jako zapytanie do uencoder podałem:
Po podmianie kolejności user() z version() otrzymałem nazwę użytkownika:
Chcemy więcej danych!
Do tej pory naszym celem było wyświetlenie wersji bazy danych i użytkownika. O ile takie dane pokazują podatność na sql injection, o tyle klient zlecający nam audyt chciałby widzieć coś więcej.
Wróćmy więc do naszego dvwa – jak pamiętamy atak się udał gdy w polu podaliśmy:
Po więcej informacji odsyłam Cię do dokumentacji – my natomiast wykorzystamy wskazaną bazę do uzyskania danych o bazie obsługującej nasz skrypt.
Zmodyfikujmy nasze zapytanie – wiemy z powyższego że baza danych obsługująca serwis nazywa się dvwa. Pobierzmy więc nazwy tabeli w naszej bazie z tabeli information_schema.tables:
Jak widać baza zawiera 2 tabele – guestbook i users. Sprawdźmy więc co znajduje się w tabeli users:
Bingo! Ponieważ widać że hasła są kodowane w MD5 możemy zdekodować je narzędziem online, lub w systemie kali linux np poprzez findmyhash:
Blind SQL Injection
Poza zwykłym sql injection istnieje jeszcze blind sql injection.
Jak sama nazwa mówi podatność typu blind charakteryzuje się tym iż nie widzimy wyniku zwrotnego a jedynie “dowód” na istnienie podatności.
Omówimy to na przykładzie dvwa:
Formularz przyjmuje id użytkownika – po wpisaniu otrzymujemy informację zwrotną na temat tego czy id znajduje sie w bazie czy nie, jednak bez szczegółowych informacji na temat podanego id.
Po wpisaniu np 1 otrzymamy komunikat:
Jak sprawdzić podatność typu blind?
Wystarczy zamiast id wprowadzić odpowiednie zapytanie ( użyjmy tego ze zwykłej podatności sql):
Nie widzimy co prawda wyniku zapytania, ale wiedza o tym iż dany formularz jest podatny jest cenną informacją dla dalszego rekonesansu.
Automatyzacja sprawdzania podatności, czyli sqlmap
Skoro mamy już wiedzę jak sprawdzać czy dany serwis jest podatny na atak sql injection czas wykorzystać narzędzie napisane do pomocy w rekonesansie, czyli sqlmap.
Jest to zaawansowany kombajn który automatycznie zweryfikuje podatność danej zmiennej, pod warunkiem że podamy mu odpowiednie parametry.
Do wykonania ataku na nasz serwis potrzebujemy adresu url oraz danych używanych w przesłanym żądaniu (parametrów). Ponieważ adres url działa tylko dla zalogowanych potrzebujemy też nasze aktualne ciasteczko sesyjne.
Pełne polecenie do weryfikacji podatności w przypadku mojego serwisu wygląda tak:
Możemy teraz dodać do naszego polecenia różne przełączniki jak np –dbs do wylistowania baz danych, –dump do wykonania kopii czy –os-shell do uzyskania powłoki na podatnym serwerze.
Używając sqlmap należy jednak pamiętać o kilku istotnych rzeczach ( nie nazwałbym ich wadami, a raczej skutkiem używania):
- Sprawdzenie podatności to wykonanie conajmniej kilkudziesięciu żądań które są zapisywane w logach, a więc możliwe do wykrycia po stronie systemu detekcji
- Skaner to nie człowiek – jak widać w powyższych przykładach często trzeba się sporo natrudzić by osiągnąć zamierzony efekt
- warto przejrzeć i zmienić niektóre ustawienia, takie jak user-agent który domyślnie widnieje jako “sqlmap/1.2.10#stable (http://sqlmap.org)”
Podsumowanie
Wystarczy jedno słabo zabezpieczone pole by pozyskać wszystkie informację z bazy danych. – należy o tym pamiętać tworząc rozwiązania, gdyż jest to jedna z najczęściej wykorzystywanych podatności.
Mimo iż pokazałem kilkanaście przykładów na uzyskanie dostępu do niepożądanych danych, istnieje jeszcze conajmniej kilka technik (celowo) nieopisanych w tym artykule na ominięcie filtrowania.
Każdemu zainteresowanemu polecam samodzielne wykonanie testów – jeżeli chciałbyś sprawdzić się w innym narzędziu niż opisane w tym wpisie polecam pobrać obraz z mutillidae, który również ma sporo podatności i kilka poziomów, lub sqli-labs:
Polecam wykonywac je albo we własnym środowisku, albo na w/w platformach testowych.
Panie Marku jak zwykle kawał solidnej wiedzy.
Dzięki
Dziękuje bardzo 🙂
Dzięki wielkie za naświetlenie w taki sposób sprawy! Przyda się przy webdeveloperce
Ciekawe ile z tych ataków przejdzie na tej stronie DVWA po zainstalwoaniu mod_security.