Przejdź do treści

SQL Injection

SQL Injection to podatność umożliwiająca wstrzyknięcie dodatkowych elementów składni języka SQL do zapytania tworzonego przez aplikację. Występuje, gdy dane pochodzące od użytkownika są bezpośrednio łączone z treścią zapytania jako tekst, zamiast być przekazywane jako jego parametry.

W wyniku udanego ataku atakujący może odczytać, zmodyfikować lub usunąć dane z bazy, obejść mechanizmy uwierzytelniania, a w sprzyjających konfiguracjach - uzyskać wykonanie kodu na serwerze lub dostęp do systemu plików.

Skutki udanego ataku

Wstrzyknięcie SQL może prowadzić do:

  • odczytu poufnych danych (dane użytkowników, hasła, tokeny, dane finansowe),
  • obejścia mechanizmu uwierzytelniania i zalogowania się bez znajomości hasła,
  • modyfikacji lub usunięcia rekordów w bazie,
  • wykonania operacji administracyjnych w bazie danych,
  • wykonania poleceń systemowych lub dalsza kompromitacja środowiska w szczególnych konfiguracjach zapewniających bazie odpowiednie uprawnienia lub niebezpieczne funkcje.

Mechanizm działania

Klasyczny przykład to formularz logowania budujący zapytanie poprzez konkatenację (dla ułatwienia pominięto proces weryfikacji hasha):

$query = "SELECT * FROM users WHERE login = '" . $_POST['login'] . "' AND password = '" . $_POST['password'] . "'";

Normalne żądanie:

login=admin
password=secret

powoduje utworzenie zapytania:

SELECT * FROM users WHERE login = 'admin' AND password = 'secret';

Atakujący może jednak przesłać:

login=admin' -- 
password=anything

wartość po -- zawiera końcową spację, wymaganą przez niektóre silniki SQL do rozpoczęcia komentarza. Aplikacja tworzy wtedy zapytanie:

SELECT * FROM users WHERE login = 'admin'-- ' AND password = 'anything'

Sekwencja -- rozpoczyna komentarz w SQL, przez co warunek hasła zostaje pominięty. Jeżeli konto admin istnieje, zalogowanie powiedzie się bez znajomości hasła.

Uwaga

Wystąpienie podatności SQL Injection nie wymaga wykorzystania widocznego elementu interfejsu użytkownika. Atakujący może posłużyć się między innymi polami ukrytymi, nagłówkami (np. HTTP, ale również metadanymi przesyłanymi w innych protokołach, takich jak WebSocket), plikami cookie, wartościami przesyłanymi w formacie JSON lub segmentami ścieżki URL. Wektorem ataku może być każda wartość wejściowa, która trafia do zapytania SQL bez bezpiecznego przekazania jej jako parametru niezależnie od protokołu czy kanału, którym została przesłana.

Typy SQL Injection

In-band (klasyczny)

Wynik zapytania lub informacja ujawniona przez błąd wraca do atakującego tym samym kanałem komunikacji, którym przekazano złośliwe dane. Może to być np. odpowiedź HTTP, komunikat WebSocket, odpowiedź API lub wynik przetwarzania danych przez inny interfejs aplikacji.

  • Error-based - atakujący celowo wywołuje błędy bazy danych, których treść może ujawniać dane, informacje o strukturze zapytania lub schemacie bazy. Dzieje się tak, ponieważ niektóre bazy danych w treści komunikatu o błędzie potrafią pokazać fragment danych, o które poprosił atakujący. Przykładowo, jeśli aplikacja wyświetla użytkownikowi surowy błąd bazy danych zamiast go ukrywać, atakujący może tak spreparować swoje zapytanie, żeby ten błąd zawierał np. nazwę aktualnie używanej bazy danych lub wersję jej silnika pomimo, że sama strona nie miała nigdzie takiej informacji wyświetlać.
  • Union-based - wykorzystuje operator UNION, aby dołączyć wyniki własnego zapytania do wyników zapytania aplikacji, np.:

    ' UNION SELECT login, password FROM users-- 
    

Blind (ślepy)

Baza nie zwraca wyników ani błędów wprost. Atakujący wnioskuje na podstawie zachowania aplikacji.

  • Boolean-based - atakujący przesyła warunki, które mogą być prawdziwe albo fałszywe i obserwuje różnice w zachowaniu aplikacji np. AND 1=1 vs AND 1=2. Każda taka obserwacja przekazuje niewielką ilość informacji. Powtarzając zapytania dotyczące kolejnych znaków lub właściwości danych, atakujący może stopniowo odtworzyć informacje z bazy, mimo że aplikacja nie zwraca ich bezpośrednio. W zależności od payloadu aplikacja zwraca np. jeden bit informacji i to na podstawie tego działania możliwa jest eksfiltracja danych.
  • Time-based - atakujący używa funkcji opóźniającej (np. SLEEP(10), pg_sleep(10), WAITFOR DELAY) i na podstawie czasu odpowiedzi ustala, czy warunek jest prawdziwy.

Charakterystyka

SQL Injection najczęściej wynika z:

  • dynamicznego tworzenia zapytań SQL przez łączenie niezaufanych danych z treścią zapytania,
  • umieszczania niezaufanych danych w elementach strukturalnych zapytania, takich jak nazwy tabel, nazwy kolumn, kierunek sortowania lub inne fragmenty składni, bez mapowania ich na stałe dozwolone wartości,
  • tworzenia dynamicznego zapytania SQL wewnątrz procedur składowanych, skryptów, zadań wykonywanych w tle lub innych komponentów przetwarzających niezaufane dane,
  • błędnego założenia, że walidacja danych, ręczna sanityzacja albo użycie określonej funkcji sterownika automatycznie zabezpiecza każde zapytanie.

Ten typ podatności występuje nie tylko w aplikacjach webowych - każdy komponent budujący SQL z niezaufanych danych może być narażony (API, zadania w tle, skrypty ETL, procedury z dynamicznym SQL).

Rekomendacje

Aby ograniczyć ryzyko występowania podatności należy:

  • stosować zapytania parametryzowane jako podstawowy sposób przekazywania wartości do SQL. Parametry są oddzielane od składni zapytania, dzięki czemu przekazane dane nie mogą zmienić jego znaczenia. Procedury składowane przetwarzające dane wejściowe również powinny używać bezpiecznej parametryzacji i unikać tworzenia dynamicznego SQL z niezaufanych danych,
  • nie umieszczać niezaufanych danych bezpośrednio w elementach strukturalnych zapytania SQL. Elementy, których dany silnik lub sterownik nie pozwala przekazać jako parametrów np. nazwy tabel i kolumn, kierunek sortowania ASC/DESC albo wybrane słowa kluczowe, powinny być wybierane przez aplikację z zamkniętej listy stałych, dozwolonych wartości. Dotyczy to również dynamicznie tworzonych fragmentów klauzul takich jak ORDER BY lub LIMIT, jeżeli nie mogą zostać bezpiecznie sparametryzowane w danym środowisku,
  • walidować dane wejściowe względem oczekiwanego typu, zakresu i formatu jako dodatkową warstwę ochrony. Walidacja nie zastępuje parametryzacji, a poprawnie zwalidowana wartość nie powinna być z tego powodu bezpośrednio dołączana do zapytania SQL,
  • stosować zasadę najmniejszych uprawnień dla konta bazy używanego przez aplikację,
  • nie zwracać użytkownikowi szczegółowych komunikatów bazy danych, treści zapytań ani pełnych tracebacków. Informacje diagnostyczne należy bezpiecznie rejestrować po stronie serwera, ograniczając dostęp do logów i usuwając z nich dane wrażliwe,
  • stosować WAF wyłącznie jako dodatkową warstwę ochrony, pamiętając, że nie zastępuje on bezpiecznego tworzenia zapytań SQL i może zostać ominięty.

Uwaga

Konkretne funkcje i możliwości zależą od silnika bazy (MySQL, PostgreSQL, MSSQL, Oracle, SQLite). Treść komunikatów błędów, dostępne funkcje (SLEEP, BENCHMARK, xp_cmdshell) oraz sposoby odczytu plików/RCE różnią się w zależności od systemu - warto to uwzględnić podczas testów i konfiguracji obrony.

Dobra praktyka

Parametryzacja zapytań jest podstawowym i skutecznym mechanizmem ochrony przed SQL Injection, dlatego powinna stanowić domyślny sposób komunikacji z bazą danych. Każde połączenie niezaufanych danych z treścią zapytania SQL należy traktować jako potencjalną zdarzenie warte weryfikcaji - nawet jeśli dane te wydają się bezpieczne, ponieważ są na przykład liczbami lub identyfikatorami pobieranymi z nagłówków.