Formy ataku SQL Injection

Transkrypt

Formy ataku SQL Injection
Formy ataku SQL Injection
Niedostateczne filtrowanie danych
Ten typ ataków opiera się na nieodpowiednim filtrowaniu znaków ucieczki z danych
wejściowych, co pozwala na przekazanie dodatkowych parametrów do zapytania.
Poniższy kod prezentuje ten problem (PHP):
$q = mysql_query("SELECT * FROM uzytkownicy WHERE uzytkownik =
'$uzytkownik'");
Gdy użytkownik przekaże jako $uzytkownik wartość "kowalski", całe zapytanie przyjmie
postać:
SELECT * FROM uzytkownicy WHERE uzytkownik = 'kowalski'
i będzie spełniało swoją funkcję. Jednak gdy złośliwy użytkownik przekaże wartość "x' OR
'1'='1" to całe zapytanie będzie wyglądało:
SELECT * FROM uzytkownicy WHERE uzytkownik = 'x' OR '1'='1'
przez co pobierze z bazy danych wszystkie rekordy zamiast jednego wybranego.
Teoretycznie w ten sposób można przekazać każde zapytanie SQL, włącznie z wykonaniem
kilku zapytań naraz. Jeżeli w powyższym przykładzie użytkownik przekaże
x';DROP TABLE uzytkownicy; SELECT * FROM data WHERE nazwa LIKE '%
to całe zapytanie przybierze postać
SELECT * FROM uzytkownicy WHERE uzytkownik = 'x';DROP TABLE uzytkownicy;
SELECT * FROM dane WHERE nazwa LIKE '%'
co zaowocuje usunięciem tabeli "uzytkownicy" i pobraniem wszystkich danych z tabeli
"dane".
Korzystając z ataku typu SQL Injection można również przeprowadzić atak typu DoS
(odmowy usługi):
x' AND BENCHMARK(9999999,BENCHMARK(999999,BENCHMARK(999999,MD5(NOW()))))=0
OR '1'='1
W tym przypadku serwer spróbuje obliczyć skrót MD5 dla aktualnego czasu 9999993 =
999997000002999999 (w zaokrągleniu trylion) razy.
"Ślepy" atak
O "ślepym" ataku (ang. Blind SQL Injection) mówi się w przypadku wykonywania ataku typu
SQL Injection na stronie, która nie wyświetla komunikatów błędów. W tym przypadku badać
można zmiany na stronie, przykładowo:
SELECT * FROM uzytkownicy WHERE uzytkownik='x' AND 1=2;
Powyższe zapytanie powinno nic nie zwrócić. Inaczej w przypadku wykonania poniższego
zapytania:
SELECT * FROM uzytkownicy WHERE uzytkownik='x' OR 1=1;
Powyższe zapytanie powinno zawsze zwrócić jakikolwiek wynik, bowiem 1 jest równe 1.
Badając zmiany zachodzące na stronie można stwierdzić czy w danym miejscu można
dokonać ataku.
Błędy w serwerze SQL
Czasami błędy umożliwiające atak występują w samym serwerze SQL, jak było w przypadku
funkcji real_escape_chars() z MySQL.
Zabezpieczanie przed atakiem
Zabezpieczanie na poziomie aplikacji
Podstawowym sposobem zabezpieczania przed SQL injection jest niedopuszczenie do
nieuprawnionej zmiany wykonywanego zapytania.
W PHP można to zrobić, poprzez wykonanie na każdym tekstowym parametrze
wykorzystywanym do budowy zapytania wbudowanej funkcji addslashes(), która dodaje
backslash przed znakami, takimi jak ', " czy \, dzięki czemu znaki te nie są traktowane jak
znaki specjalne. Dostępne są również funkcje specyficzne dla poszczególnych silników, takie
jak np. oferowana przez serwer MySQL mysql_real_escape_string(). Jeśli wczytując
dane z formularza programista oczekuje wartości liczbowych, może korzystać z funkcji
is_numeric(zmienna), która sprawdza czy zmienna jest wartością numeryczną. Po
sprawdzeniu zmiennej i upewnieniu się, iż jest liczbą, można bezpiecznie użyć zmiennej w
zapytaniu SQL.
Analogicznie do addslashes(), w perlowym DBI istnieje metoda DBI::quote, która,
podobnie jak addslashes() z PHP dodaje znaki backslash przed potencjalnie
niebezpiecznymi znakami. Przykład (zakładając, że $sql jest referencją do obiektu DBI):
$query = $sql->prepare("SELECT * FROM uzytkownicy where name = " . $sql>quote($uzytkownik));
Bezpieczniejszą techniką, niż wyżej wymienione odpowiednie przygotowanie parametrów
jest użycie mechanizmu tzw. "zaślepek", gdzie zmienne nie są używane bezpośrednio do
tworzenia zapytania, a odpowiednie dane dołączane są do zapytania w momencie jego
wykonania (czy to poprzez wykorzystanie API danego silnika, czy też w ramach warstwy
aplikacji poprzez odpowiednie zacytowanie wszystkich parametrów). Przykład w Perl DBI:
$query = $sql->prepare("select * from users where name = ?");
$query->execute($user_name);
Jeżeli parametr zapytania ma być wartością liczbową można go po prostu rzutować na typ
liczbowy lub użyć funkcji konwertujących z ciągu znaków na wartość liczbową.
Techniką utrudniającą wykorzystanie istniejących luk jest zastosowanie paradygmatu security
through obscurity poprzez wyłączenie wyświetlania komunikatów o błędach. Nie
uniemożliwi to ataku, lecz może spowodować, że trudniejsze będzie wykrycie i późniejsze
wykorzystanie luki.
Zabezpieczenie na poziomie bazy danych
Istnieją również metody zabezpieczenia przed skutkami wykonania błędnych zapytań, które
mimo wszystko dostaną się do bazy.
Udostępnienie użytkownikowi bazy tylko niezbędnych uprawnień nie da całkowitej ochrony,
jednak pozwoli na minimalizację szkód. Przykładowo – niewiele aplikacji potrzebuje
uprawnienia do kasowania tabel z bazy danych. Podobny efekt – czyli zmniejszenie
możliwości atakującego – uzyskać można wyłączając niepotrzebną funkcjonalność na
poziomie samego silnika.
Pomocą mogą służyć również procedury składowane, dzięki którym zapytanie budowane jest
po stronie bazy danych i aplikacja nie ma bezpośredniego wpływu na jego postać. Chociaż i
w tym przypadku skonstruowanie ataku nie jest niemożliwe.
Eliminację możliwości wstrzyknięcia kodu SQL, można uzyskać poprzez całkowite
wyłączenie możliwości podawania parametrów jako części zapytania. Jest to działanie
analogiczne do mechanizmu zaślepek po stronie aplikacji, jednak tym razem zastosowane w
samym silniku. Nie jest to jednak ogólnie dostępna cecha systemów zarządzania bazami. W
chwili obecnej, możliwość taką daje silnik H2.
Przykłady ataków SQL Injection
Atak SQL Injection wykorzystuje fakt że dane wpisane do formularza na stronie WWW (lub
w inny sposób wysłane do serwera) nie są w żaden sposób walidowane pod kątem ich
poprawności. Do tego dochodzi fakt że w zapytaniach SQL stringi należy zamknąć pomiędzy
znakami apostrofu. Zatem jeżeli ktoś wpisze do formularza ciąg znaków zawierający m.in.
apostrof, różne rzeczy mogą się przytrafić - od zwykłego błędu wykonania zapytania SQL, aż
po wykasowanie wszystkich danych z bazy danych czy dysku serwera, albo włamania do
innych maszyn w sieci!
Najprostszy atak SQL Injection może wyglądać następująco: załóżmy że na stronie znajduje
się następujący formularz:
<form method="post" action="login.php">
Login: <input type="text" name="login"><br>
Hasło: <input type="password" name="pass"><br>
<input type="submit" value="Zaloguj się"><br>
</form>
Po stronie serwera, w kodzie PHP tworzone i wykonywane jest następujące zapytanie:
SELECT USER_ID FROM USERS WHERE (LOGIN='$login') AND (PASS='$pass')
Zmienne $login i $pass zawierają odpowiednio login i hasło wpisane w formularzu.
Załóżmy że do systemu loguje się admin, posługując się swym bezpiecznym hasłem
"jh*8!aaC4". Zapytanie które zostanie wykonane wygląda następująco:
SELECT USER_ID FROM USERS WHERE (LOGIN='admin') AND (PASS='jh*8!aaC4')
Jak na razie wszystko wygląda w porządku. Załóżmy jednak że nasz Czarny Kapelusz (Black
Hat) próbuje się włamać, i wpisał jako hasło:
' OR '1'='1
Wynikowe zapytanie które zostanie wykonane będzie następujące:
SELECT USER_ID FROM USERS WHERE (LOGIN='admin') AND (PASS='' OR '1'='1')
Jak widać, w zapytaniu zmienił się warunek który sprawdza hasło - obecnie składa się on z
sumy logicznej dwóch warunków: pierwszego który jest fałszywy (no chyba że admin nie ma
założonego hasła, co jest mało prawdopodobne), i drugiego który jest zawsze prawdziwy.
Wynik: napastnik zalogował się na konto admina bez znajomości (!) jego hasła.
Powyższy przykład ataku można zmodyfikować, wpisując następujący ciąg znaków jako
nazwę użytkownika, i dowolne hasło (np. "a"):
admin') --
Wynikowe zapytanie które zostanie wykonane będzie następujące:
SELECT USER_ID FROM USERS WHERE (LOGIN='admin') --') AND (PASS='a')
W języku SQL dwa myślniki umieszczone obok siebie oznaczają początek komentarza, zatem
w rezultacie wykona się tylko pierwsza część warunku WHERE (sprawdzenie nazwy
użytkownika).
Napastnikowi może także nie zależeć na uzyskaniu nieautoryzowanego dostępu do serwera,
ale na wyrządzeniu jak największych szkód. Może on tu skorzystać z faktu, że baza danych
może wykonać kilka zapytań podanych po kolei (i ew. rozdzielonych średnikiem).
Przykładowo może on wpisać coś takiego jako nazwę użytkownika:
') DELETE FROM USERS --
Wynik jest mało przyjemny - skasowana zostanie zawartość tabeli USERS. Napastnik może
posunąć się jeszcze dalej, wpisując coś takiego:
') DROP DATABASE SKLEP --
Po wykonaniu tej komendy cała nasza baza danych przestaje istnieć! Jeżeli baza danych
pozwala na wykonywanie poleceń systemowych, napastnik może także wykasować zawartość
twardego dysku. Np. źle skonfigurowana baza danych MS SQL Server na Windows może
pozwolić na wykonanie takiego ataku:
'); exec master..xp_cmdshell 'iisreset /stop' --
Wpisane powyższego ciągu znaków jako nazwy użytkownika spowoduje zatrzymanie serwera
IIS (Internet Infomation Server, czyli serwer WWW Microsoftu). Jakie to może mieć skutki
np. dla sklepu internetowego chyba nie muszę tłumaczyć. Poza tym w systemie istnieje cały
szereg innych komend które napastnik może wykorzystać do złych celów.
Celem napastnika może nie być sianie zniszczenia, ale chęć kradzieży poufnych danych.
Załóżmy że sklep internetowy pozwala na wyświetlenie listy produktów które zamówiło się w
ostatnim czasie, za pomocą zapytania:
SELECT PRODUKT_ID, NAZWA, OPIS, KWOTA
FROM PRODUKTY, ZAMOWIENIA
WHERE (PRODUKTY.PRODUKT_ID=ZAMOWIENIA.PRODUKT.ID)
AND (KLIENT_ID=$id)
ORDER BY NAZWA
W tym przykładzie identyfikator klienta jest przekazywany z zewnątrz. W normalnej sytuacji
jest to liczba; jeżeli jednak napastnik wpisze coś takiego:
0 OR 1=1
wtedy ostatnia linia warunku WHERE przybierze następującą postać:
AND (KLIENT_ID=0 OR 1=1)
Wynik: cała historia zamówień dokonanych przez wszystkich klientów sklepu wyświetli się
na stronie.
Napastnik może także dostać się do innych tabel, korzystając z instrukcji UNION. W tym celu
może wpisać np. coś takiego jako id:
-1) UNION SELECT KLIENT_ID, LOGIN, HASLO, 0 FROM KLIENCI --
Ostatnia linia warunku będzie wyglądać następująco:
AND (KLIENT_ID=-1) UNION SELECT KLIENT_ID, LOGIN, HASLO, 0 FROM KLIENCI --)
Zero w drugim zapytaniu SELECT po UNION jest konieczne aby liczba kolumn i ich typy się
zgadzały. Całe zapytanie po wykonaniu zwróci napastnikowi listę loginów i haseł wszystkich
klientów sklepu (być może także personelu, a przy odrobinie szczęścia nawet i hasło admina).
Stąd już prosta droga do kradzieży np. danych osobowych.
Na tym możliwości ataków się nie kończą - korzystając z SQL Injection oraz faktu że strona
ASP nie sprawdza potencjalnych błędów bazy danych można szukać innych tabel w bazie
danych, oraz próbować określić typy ich kolumn - więcej na ten temat jest w materiałach do
których linki są podane na końcu.
Zabezpieczenie przed atakiem SQL Injection
Aby zapobiec tego typu atakom, należy traktować wszystkie dane otrzymane z zewnątrz jako
niezaufane, i sprawdzać ich poprawność. W przypadku liczb doskonale nadają się do tego np.
wyrażenia regularne - w powyższym przykładzie parametr id można sprawdzić używając
poniższego warunku (sprawdza on czy string składa się tylko z cyfr):
if (!ereg('^\d+$', $id))
{
// obsługa niepoprawnych danych
}
W przypadku danych tekstowych (jak np. login czy hasło) należy odpowiednio zakodować
wszystkie pojedyncze apostrofy (zazwyczaj zamienić je na parę apostrofów), przez co
ewentualny atak zostanie unieszkodliwiony. Dla każdej obsługiwanej bazy danych PHP
dostarcza odpowiednią funkcję dedykowaną do tego celu, np. dla bazy MySQL jest to funkcja
mysql_escape_string(). Można ją użyć następująco:
$login = mysql_escape_string($login);
W przypadku stringów można także rozważyć czy znak apostrofu może wystąpić w
"legalnych" danych wejściowych - jeżeli nie, można dodać warunek który będzie odrzucał
takie dane. W każdym przypadku warto także określić maksymalną długość danych
wejściowych, i ją też kontrolować.
Jeżeli dane tekstowe są przeznaczone do późniejszego wyświetlenia na stronie WWW (a tak
jest w większości przypadków), należy pamiętać aby przed ich wyświetleniem zamienić
wszystkie znaki specjalne HTML na ich zakodowane odpowiedniki (np. za pomocą funkcji
htmlspecialchars), aby zapobiec wyświetleniu fragmentu kodu HTML (lub gorzej, skryptu
JavaScript, co może być atakiem XSS - Cross Site Scripting). "Legalne" dane też mogą
niekiedy powodować podobne problemy. Dane można zakodować już przed zapisem ich do
bazy danych. Warto wybrać jedną z tych dwóch metod (kodowanie przed wyświetleniem, lub
zapisem do bazy danych), i konsekwentnie się jej trzymać.
Jeżeli używany interfejs dostępu do bazy danych obsługuje mechanizm parametrów, warto z
niego skorzystać. Przedtem jednak należy się upewnić że on jest bezpieczny (tzn. parametry
nie są wstawiane bezpośrednio do tekstu zapytania).
mod_rewrite jako dodatkowe zabezpieczenie przed SQL
Injection
Dodatkowym metodą zabezpieczenia utrudniającą włamanie przez SQL Injection jest
zastosowanie modułu Apache'a o nazwie mod_rewrite. Moduł ten pozwala m.in. na
wykonanie przekierowania przychodzącego żądania pod zupełnie inny adres na serwerze.
Sztuczka polega tutaj na tym że jest to wykonywane "wewnątrz" serwera, zatem nie jest to
widoczne z zewnątrz. Z tego powodu moduł ten jest często stosowany jako jeden z elementów
optymalizacji strony pod kątem wyszukiwarek internetowych. Nam jednak bardziej przyda się
fakt że poprzez modyfikację adresu URL strony można ukryć oryginalną nazwę skryptu i
wykorzystywane parametry przed wzrokiem ciekawskich. Przykład: strona wyświetlająca
dane o produktach ma następujący adres:
http://www.serwer.pl/produkty.php?kategoria=3&produkt=235
Aby to zmienić, należy dodać odpowiednie komendy do pliku .htaccess, np. takie:
RewriteEngine On
RewriteBase /
RewriteRule ^produkty,([0-9]+),([0-9]+)\.html$
produkty.php?kategoria=$1&produkt=$2 [L]
Dzięki umieszczeniu powyższych reguł w pliki .htaccess do strony z produktami będzie się
można odwołać na dwa sposoby: używając adresu podanego wcześniej, lub też nowego
wykorzystującego reguły dla mod_rewrite:
http://www.serwer.pl/produkty,3,235.html
Taki adres ma tą zaletę z punktu widzenia bezpieczeństwa że ukryte są nazwa skryptu i jego
parametry. Dodatkowo zastosowane wyrażenie regularne dokładnie określa zakres
dopuszczalnych znaków będących identyfikatorami kategorii i produktu (mają to być liczby),
więc próby wpisywania czegokolwiek innego gdzieś w środku adresu lub na jego końcu będą
skutkować błędem 404 (nie znaleziono strony).
Oczywiście przedstawione tutaj zabezpieczenie przed SQL Injection wykorzystujące
mod_rewrite nie jest idealnym zabezpieczeniem przed SQL Injection - ciągle można się
odwołać do skryptu używając jego oryginalnej nazwy i parametrów, zatem skrypt ten musi
być również odporny na atak SQL Injection. Warto go jednak zastosować jako dodatkowe
zabezpieczenie, chociażby z tego względu, że powstrzyma większość dzieciaków które
przeczytały sobie w Internecie coś na temat hakingu i potem chcą zaszpanować przed
znajomymi jaki to on czy ona jest wielki "H4k3R".
Wielofazowy atak SQL Injection
We wszystkich wcześniejszych przykładach atakujący korzystał z faktu że dane wejściowe
nie były walidowane, przez co miał możliwość modyfikacji wykonywanego zapytania. Jeżeli
jednak witryna stosuje zabezpieczenia podane powyżej, atakujący może poszukiwać drogi
ataku wielofazowego. Polega on na tym, że w pierwszej fazie dane podane przez napastnika
są zapisywane do bazy danych, z uwzględnieniem powyższych zaleceń - mogą to być np.
kryteria wyszukiwania do raportów które użytkownik może wygenerować, czy sposób
sortowania wyników. Użytkownik może wybrać sobie jedno z tak zapisanych np. kryteriów
wyszukiwania poprzez wybór jego nazwy - wydaje się to bezpieczne, gdyż do serwera jest
przesyłany wyłącznie identyfikator, który podlega sprawdzeniu czy jest poprawny. Następnie
tworzone jest dynamicznie zapytanie, którego częścią jest to co zostało wcześniej zapisane w
bazie danych. W tym momencie twórcy witryny WWW mogą popełnić błąd zakładając że
dane pochodzące z bazy danych są "bezpieczne". Jest to błąd, gdyż apostrofy które zostały
zamienione na pary apostrofów podczas zapisu do bazy, przy odczycie ich z bazy są zwracane
ponownie jako pojedyncze apostrofy
Aby się zabezpieczyć przed takimi niespodziankami, należy uważać w sytuacji gdy dane
tekstowe pochodzące z bazy danych mają zostać użyte do konstrukcji kolejnych zapytań, i
odpowiednio je zakodować. Alternatywnie można także rozważyć zamianę wszystkich
apostrofów na cztery apostrofy podczas pierwszego zapisu do bazy (podczas odczytu zostanie
tylko para apostrofów, czyli nadal to będzie bezpieczne) - jeżeli to rozwiązanie będzie
stosowane konsekwentnie, powinno być także bezpieczne. Należy jednak w takim wypadku
pilnować aby nie dopuścić do możliwości przeprowadzenia ataku 3- lub więcej fazowego.
Osobom zainteresowanym tym tematem polecam także przejrzenie następujących materiałów:



SQL Injection: Modes of Attack, Defence, and Why It Matters - opis sposobu
ustalenia danych o kolumnach używanych w zapytaniu;
Advanced SQL Injection - zaawansowane techniki ataków SQL Injection na SQL
Server;
Advanced SQL Injection - do zrozumienia tej prezentacji co prawda wymagana jest
pewna wiedza, ale i tak czytając nagłówki można się zorientować jakie zagrożenia
niosą ze sobą ataki SQL Injection. Znajduje się tam porównanie różnych baz danych
pod kątem podatności na ataki SQL Injection, oraz szereg różnych metod ataków.