SQL Injection

Co to jest?

Ataki typu SQL injection polegają na wykorzystaniu błędów w wykorzystaniu języków skryptowych do przekazywania danych od użytkownika bezpośrednio do bazy. Ataki tego typu są przeprowadzane pomiędzy warstwą programistyczną a warstwą danych. Wobec tego bezradne są im mechanizmy bezpieczeństwa działające na niskim poziomie. Aby doszło do SQL injection, w atakowanej aplikacji muszą być wykorzystywane mechanizmy dynamicznego generowania zapytań SQL. Języki, interfejsy i narzędzia, w których może to wystąpić to między innymi: JSP, ASP, XML, XSL, XSQL, Perl, CGI, JavaScript, VB, narzędzia firm produkujących bazy danych, języki wyższego rzędu, jak C, COBOLitd.

Przyklad 1

Za pierwszy przykład niech posłuży skrypt w Java Server Pages połączony z bazą Oracle. Wyobraźmy sobie formularz składający się z jednego pola tekstowego. Jeżeli w polu formularza wpiszemy: sal=800 , jako wynik otrzymamy listę pracowników, których wynagrodzenie (pole SAL ) wynosi 800. Rezultaty są generowane przez odpowiednie zapytanie SQL wykonywane bezpośrednio na bazie danych. To, co użytkownik wpisuje za pośrednictwem formularza WWW, jest jedynie "doklejane" do już zdefiniowanej formuły.

SELECT ename, sal FROM scott.emp WHERE sal=800 

Zapytanie SQL jest tworzone za pomocą konkatenacji dwóch ciągów znaków - jeden jest zapisany na stałe w skrypcie, zaś drugi pobierany jest od użytkownika. Problem pojawia się gdy użytkownik jest w stanie wpisywać w formularzu dowolne ciągi znaków. Wtedy w polu formularza wystarczy wpisać poniższy ciąg znaków:

sal=800 union select username, user_id from all_users 

W wyniku działania skryptu JSP, polegającego na sklejeniu dwóch ciągów znaków, w bazie zostanie wykonane następujące polecenie:

SELECT ename, sal FROM scott.emp WHERE sal=800
UNION SELECT username, user_id FROM all_users 

W ten sposób, użytkownik (intruz) uzyskuje dostęp do widoku all_users, na co twórca interfejsu nie zezwolił bezpośrednio. Jego błąd polegał jednak na tym, że nie ograniczył tego przywileju.

Czym grozi atak SQL Injection?

Atak SQL injection może spowodować dostęp intruza do dowolnych danych w bazie, jak również stwarza możliwość modyfikacji i usunięcia danych z bazy. Można zatem w taki sposób wykraść numery kart kredytowych i inne dane o użytkownikach. Jest to atak wysokiego poziomu. Omija firewalle i ogólnie wszelkie zabezpieczenia na poziomie IP. Bezużyteczne okazują się także, działające w wyższych warstwach, skanery antywirusowe oraz filtry treści wykorzystujące bazy sygnatur. Ataki te nie wykorzystują bowiem znanych błędów w oprogramowaniu, dla których istnieją sygnatury, np. serwerach WWW czy serwerach baz danych, lecz błędy popełnione przez projektantów i programistów tworzących przy ich pomocy konkretne rozwiązania, np. internetowa aplikacja do obsługi zamówień w konkretnej firmie.
Poszczególne możliwości ataku różnią się w zależności od bazy danych. Na przykład w przypadku baz danych Oracle możliwe są między innymi następujące ingerencje:

Nie są natomiast wykonalne:

Przykład 2

Przy zachowaniu pewnych warunków, technika SQL injection może służyć do modyfikowania danych. Ten przykład wykorzystuje skrypty PHP i bazę PostgreSQL. Oto kod źródłowy:

LoginPage.php 
<HTML>
<BODY>
<FORM ACTION=LoginPage2.php> 
Użytkownik: <INPUT NAME="username"><BR> 
Hasło: <INPUT NAME="password"><BR> 
<INPUT TYPE="submit" VALUE="Zaloguj"> 
</FORM> 

Formularz pobiera od użytkownika parametry username i password , a następnie wywołuje skrypt LoginPage2.php , którego rola polega na sprawdzeniu zgodności podanych przez użytkownika identyfikatorów z tymi przechowywanymi w bazie danych. Poniższy listing przedstawia kluczową część tego skryptu - funkcję przeszukującą bazę danych.

LoginPage2.php 
... 
// funkcja do autoryzacji 
function MyAuth( $conn,$username,$password) { 
$query = "SELECT id FROM users WHERE"; 
$query .= " username = '" . $username . "' AND "; 
$query .= " password = '" . $password . "'"; 
$res = pg_query( $query); 
if (pg_num_rows( $res) == 1) { 
$row = pg_fetch_array( $res); 
$id = $row['id']; 
} else { 
$id = 0; 
} 

Krytycznym fragmentem powyższego kodu jest zapytanie:

"SELECT id FROM users WHERE username = '" . $username . "' AND password = '" . $password ."'"; 

Podobnie jak w poprzednim przykładzie, zapytanie jest konstruowane przez sklejenie zaszytych w programie części stałych z elementami wprowadzonymi przez użytkownika za pośrednictwem formularza WWW . Operatorem konkatenacji w języku PHP jest znak kropki. Cudzysłowy ograniczają poszczególne ciągi znaków (string), a znaki apostrofu (') otaczają fragmenty danych wprowadzane przez użytkownika.
Wyobraźmy sobie taki atak: Użytkownik prowadza do formularza LoginPage.php dane:

Użytkownik: ';delete from users--
Hasło:

Teraz kluczowy fragment kodu PHP będzie wyglądał tak:

"SELECT id FROM users WHERE username = '" . '; delete from users--" . "' AND password = '" . . "'"; 

A ponieważ:
' zakończenie pojedynczego cudzysłowu, w który są ujmowane dane wprowadzane z wewnątrz,
; Zakończenie zapytania i rozpoczęcie nowego,
-- Początek komentarza - w ten sposób usuwa się dalszą część kodu, która nie jest intruzowi potrzebna, a może wywołać błąd składniowy i uniemożliwić wykonanie ataku,
to po przetworzeniu kodu i zinterpretowaniu powyższych znaków, otrzymujemy dwa zapytania:

 SELECT id FROM users WHERE username = ''; delete from users 

W rezultacie zawartość tablicy users zostanie usunięta, uniemożliwiając innym użytkownikom dostęp do systemu. W podobny sposób można doklejać dowolne zapytania, o ile pozwala na to API i składnia bazy danych.

Metody zapobiegania

W kwestii obrony przed atakami SQL injection trzeba podkreślić odpowiedzialność projektantów i programistów za skonstruowanie odpowiednich interfejsów dostępu do danych. Ogólne wskazówki są następujące:

Wszystkie dane pochodzące z zewnątrz aplikacji powinny być filtrowane. Procedury powinny przepuszczać tylko te znaki/ciągi, które są dopuszczalne w danym kontekście. Szczególnie ważne jest wyszukiwanie słów kluczowych, takich jak SELECT . W powyższych przykładach uniemożliwiłoby to umieszczenie w ciągu znaków znaków specjalnych takich jak cudzysłów, apostrof czy podwójny myślnik, a więc zapobiegłoby atakowi.

Przy nadawaniu uprawnień należy stosować zasadę najmniejszych przywilejów. W fazie projektowania należy zdefiniować najmniejszy zestaw informacji niezbędny do wykonania danego zadania. Szczególną uwagę należy zwrócić na prawa dostępu do wewnętrznych struktur bazy danych. W przykładzie 1 można było np. odczytać zawartość widoku all_users , chociaż przywileje te były zbędne do prawidłowego działania programu.

Precyzyjne określenie funkcji dostępnych użytkownikowi za pośrednictwem interfejsu. Operacje te należy precyzyjnie udokumentować. W przypadku operacji modyfikacji lub dodawania rekordów w bazie danych należy zamknąć ich działanie w osobnych funkcjach, separując operacje dostępu do bazy danych od warstwy prezentacji. Zgodnie z zasadą pięrwszą, każda taka funkcja powinna mieć ściśle zdefiniowane parametry wejściowe.

W środowisku aplikacji nie powinno być żadnych zbędnych do jej działania plików. Bardzo często zdarza się, że programiści, edytując dla przykładu plik adduser.php, zapominają, iż w trakcie pracy zachowali jego wcześniejszą wersję pod nazwą adduser.php.bak lub adduser.bak. W takim przypadku potencjalny intruz może bardzo łatwo odszukaś taki plik i uzyskać z niego szczegółowe informacje na temat struktury i procedur dostępu do bazy danych.

Przykład 3 - struktura danych na celowniku

Prawdopodobieństwo skuteczności ataku jest tym większe, im bardziej szczegółowa jest wiedza intruza na temat struktury bazy danych, a więc nazw tabel i kolumn oraz kształtu relacji między tabelami. Informacje o strukturze bazy można nierzadko pozyskać, konstruując błędne polecenia SQL. Serwer zwraca często komunikat o błędzie, w którym można znaleźć informacje wskazujące na strukturę bazy. Jeżeli, wracając do przykładu 2 w polu username zostanie wpisany ciąg znaków:

 Username: ' HAVING 1 = 1-- 

do bazy zostanie wysłane takie zapytanie:

 SELECT id FROM users WHERE username = '' HAVING 1 = 1 

Wyrażenie to jest poprawne składniowo, jest jednak niepoprawne ze względu na strukturę danych. W rezultacie baza zwróci następujący komunikat:

 Attribute users.id must be GROUPed or used in an aggregate function. 

W ten sposób intruz uzyskał informacje o nazwie tablicy oraz o nazwie pierwszej kolumny. Nazwy kolejnych kolumn można uzyskać np. przez odpowiednie doklejanie klauzuli GROUP BY.

Podsumowanie

Aby zmniejszyć ryzyko ataku typu SQL injection, warto przestrzegać kilku zasad:

Tematy pokrewne

HTML injection - technika wynikająca z niedostatecznego sprawdzania danych wejściowych przez aplikacje webowe. Pozwalaja ona atakującemu na zostawianie swoich pułapek na zaufanych stronach, kradzież danych z formularzy, cookies itd.

Second-order code injection atak, który polega na dostarczeniu aplikacji webowej potencjalnie niebezpiecznego kodu, który jednak nie jest od razu wykonywany, lecz zostaje przechowany przez aplikację (np. w pamięci cache lub w bazie danych) i dopiero później wczytany i uruchomiony przez ofiarę ataku.

Prezentacja na Systemy Operacyjne 2004/2005 - Informatyka MIMUW.
Wojciech Sikora-Kobyliński (O nas)