UWAGA: Poniższy artykuł zawiera informacje których użycie bez należytej ostrożności może spowodować utratę danych. Autor artykułu nie ponosi żadnej odpowiedzialności za ew. utratę danych wskutek celowego bądź przypadkowego działania. Wszystkie zamieszczone tu informacje są podane jedynie w celach edukacyjnych.

SQL Injection – jeżeli masz do czynienia z bazami danych, zapewne to hasło nie jest Ci obce. Jeżeli jednak Twoja wiedza ogranicza się tylko do kilku mętnych faktów, bądź co gorsza nie wiesz co to w ogóle jest, czas najwyższy uzupełnić wiedzę! Osoby zaznajomione z kwestiami bezpieczeństwa (nie tylko komputerowego) wiedzą że zabezpieczenia są tak skuteczne jak najsłabszy element całego systemu zabezpieczeń. Nie pozwól aby Twoja Strona WWW lub program był tym najsłabszym ogniwem!

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.