LIVE: New phishing campaigns targeting mobile users —View latest threats →

Back to Tutorials
Zaawansowany 14 min czytania

SQL injection: jak działa i jak go powstrzymać

SQL injection po dekadach wciąż należy do najczęściej wykorzystywanych podatności. Zobacz, jak atak wyciąga rekordy z bazy danych, i poznaj zabezpieczenia, które go powstrzymują.

10 stycznia 2026

Dlaczego SQL injection wciąż istnieje

SQL injection po raz pierwszy opisano w 1998 roku. Pojawia się na każdej liście OWASP Top 10 od początku jej istnienia. Odpowiada za jedne z największych wycieków danych w historii — TalkTalk (4 mln rekordów), Heartland Payment Systems (130 mln numerów kart) i niezliczone inne.

Nie przetrwał z powodu złożoności. SQL injection jest prosty do zrozumienia i, co kluczowe, prosty do zapobieżenia. Przetrwał, bo programiści sklejają dane od użytkownika z zapytaniami SQL, nie myśląc o konsekwencjach — często pod presją czasu i bez szkoleń z bezpieczeństwa.

Ten poradnik pokazuje dokładnie, jak działa, i konkretne techniki, które go eliminują.

Jak normalnie działa zapytanie SQL

Weźmy formularz logowania. Aplikacja pobiera nazwę użytkownika i hasło i sprawdza je w bazie danych:

SELECT * FROM users WHERE username = 'alice' AND password = 'secretpass';

Jeśli zapytanie zwróci wiersz, użytkownik jest uwierzytelniony. To normalne, oczekiwane zachowanie.

Podatność pojawia się, gdy programista buduje to zapytanie, sklejając ciągi znaków bezpośrednio z danych wpisanych przez użytkownika:

# Vulnerable Python code
query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "';"

Zakłada się, że użytkownicy podadzą normalne wartości. Atakujący tego nie robią.

Atak: wyjście poza ciąg znaków

SQL używa apostrofów do oznaczania wartości tekstowych. Jeśli atakujący poda nazwę użytkownika zawierającą apostrof, może wyjść poza zamierzony ciąg znaków i wstrzyknąć własny kod SQL.

Dane wpisane przez atakującego:

  • Nazwa użytkownika: ' OR '1'='1
  • Hasło: anything

Powstałe zapytanie:

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything';

Ponieważ OR '1'='1' jest zawsze prawdziwe, zapytanie zwraca wszystkich użytkowników. Logowanie udaje się na konto pierwszego użytkownika w bazie — zwykle administratora.

Całkowite pominięcie sprawdzania hasła:

  • Nazwa użytkownika: admin'--
  • -- to komentarz w SQL; wszystko po nim jest ignorowane
SELECT * FROM users WHERE username = 'admin'--' AND password = 'anything';
-- Becomes:
SELECT * FROM users WHERE username = 'admin';

Uwierzytelnianie zostaje ominięte. Pole hasła nigdy nie jest sprawdzane.

Nie tylko logowanie: wyciąganie danych

SQL injection nie ogranicza się do obejścia uwierzytelniania. Poważniejsze ataki wyciągają dane z całej bazy.

Wstrzykiwanie z UNION

Operator SQL UNION łączy wyniki dwóch zapytań SELECT. Atakujący może go użyć do pobrania danych z innych tabel:

-- Original query
SELECT name, description FROM products WHERE id = 1;

-- Injected
SELECT name, description FROM products WHERE id = 1
UNION SELECT username, password FROM users--;

Do wyników produktów zwracanych atakującemu zostają dołączone wszystkie nazwy użytkowników i hasła.

Blind SQL injection (wstrzykiwanie na ślepo)

Wiele aplikacji nie wyświetla użytkownikom błędów bazy ani wyników zapytań. Blind SQL injection wyciąga dane bit po bicie, zadając pytania typu prawda/fałsz:

-- Is the first character of the admin password 'a'?
SELECT * FROM users WHERE id = 1 AND SUBSTRING(password, 1, 1) = 'a';

Zapytanie sprawdza, czy pierwszy znak hasła administratora to „a”. Jeśli strona zachowuje się inaczej, gdy warunek jest prawdziwy, a inaczej, gdy fałszywy, atakujący może wywnioskować odpowiedź. Zautomatyzowane narzędzia, takie jak sqlmap, potrafią w ten sposób wyciągnąć całą bazę w kilka minut.

Kanały pozapasmowe i zapytania łączone

W niektórych systemach bazodanowych atakujący mogą:

  • Wykonywać wiele poleceń naraz (stacked queries): usuwać tabele, tworzyć konta-furtki
  • Odczytywać pliki z systemu plików serwera (LOAD_FILE() w MySQL)
  • Zapisywać pliki na serwerze (potencjalnie tworząc web shell)
  • Wywoływać połączenia sieciowe, by wyprowadzić dane kanałem pozapasmowym

W najgorszym przypadku SQL injection oznacza całkowite przejęcie serwera, a nie tylko ujawnienie danych.

Zabezpieczenia, które naprawdę działają

Zabezpieczenie 1: zapytania parametryzowane (prepared statements)

To podstawowa ochrona, która eliminuje SQL injection, a nie tylko ją ogranicza.

W zapytaniu parametryzowanym kod SQL i dane są wysyłane do bazy osobno. Baza najpierw kompiluje szablon SQL, a potem podstawia wartości od użytkownika. Wartości nigdy nie mogą zostać zinterpretowane jako SQL — zawsze są traktowane jako dane.

Python z zapytaniem parametryzowanym:

# Safe
cursor.execute(
    "SELECT * FROM users WHERE username = %s AND password = %s",
    (username, password)
)

Java z PreparedStatement:

PreparedStatement stmt = conn.prepareStatement(
    "SELECT * FROM users WHERE username = ? AND password = ?"
);
stmt.setString(1, username);
stmt.setString(2, password);

Bez względu na to, co użytkownik wpisze w pole username — łącznie z ' OR '1'='1 — zostanie to potraktowane jako dosłowna wartość tekstowa, a nie SQL. Wstrzyknięcie jest niemożliwe.

Zabezpieczenie 2: ORM i konstruktory zapytań

Mapery obiektowo-relacyjne (ORM), takie jak SQLAlchemy, Hibernate, ActiveRecord czy Django ORM, wewnętrznie używają zapytań parametryzowanych. Prawidłowe korzystanie z ORM zapobiega SQL injection niemal we wszystkich przypadkach:

# Django ORM — safe
User.objects.filter(username=username, password=password)

Uwaga: większość ORM-ów ma furtki do surowego SQL (raw(), execute()), które ponownie wprowadzają ryzyko, jeśli dane użytkownika są w nich sklejane. Traktuj metody surowego SQL z taką samą ostrożnością jak ręczne budowanie zapytań.

Zabezpieczenie 3: procedury składowane

Procedury składowane mogą być bezpieczne, jeśli są poprawnie zaimplementowane — muszą wewnętrznie używać parametryzowanych danych wejściowych. Procedura, która wewnętrznie skleja ciągi znaków, wciąż jest podatna.

Zabezpieczenie 4: zasada najmniejszych uprawnień

Konto bazodanowe używane przez aplikację webową powinno mieć tylko niezbędne uprawnienia:

  • Aplikacja głównie odczytująca dane potrzebuje tylko SELECT
  • Żadna aplikacja nie powinna działać jako root, sa czy DBA
  • Różne komponenty aplikacji mogą korzystać z różnych kont bazodanowych z różnymi uprawnieniami

To ogranicza skutki udanego wstrzyknięcia: atakujący, który może tylko wykonywać SELECT, nie usunie tabel ani nie zapisze plików.

Zabezpieczenie 5: walidacja danych wejściowych

Sprawdzaj, czy dane wejściowe mają oczekiwany format:

  • Identyfikator użytkownika powinien być dodatnią liczbą całkowitą — odrzuć wszystko inne, zanim trafi do zapytania
  • Nazwa produktu nie powinna zawierać słów kluczowych SQL — stosuj listę dozwolonych znaków

Walidacja danych wejściowych to środek obrony w głąb, a nie podstawowe zabezpieczenie. Zmniejsza powierzchnię ataku, ale nie powinna być jedyną ochroną.

Zabezpieczenie 6: WAF (zapora aplikacji webowych)

WAF może wykrywać i blokować typowe wzorce SQL injection. To dodatkowa warstwa, a nie zamiennik zapytań parametryzowanych. WAF-y da się obejść sztuczkami z zaciemnianiem i kodowaniem. Nie polegaj na nich jako jedynej ochronie.

Testowanie pod kątem SQL injection

Podstawy testów ręcznych:

  1. Wpisz pojedynczy apostrof (') w każde pole — komunikaty błędów bazy często ujawniają miejsca podatne na wstrzyknięcie
  2. W parametrach liczbowych spróbuj 1=1 i 1=2 i porównaj odpowiedzi
  3. Spróbuj '; SELECT SLEEP(5);-- — jeśli odpowiedź się opóźni, występuje blind injection

Narzędzia automatyczne:

  • sqlmap — standardowe narzędzie do wykrywania i wykorzystywania podatności (tylko w systemach, które masz prawo testować)
  • Burp Suite — przechwytywanie i modyfikowanie żądań; w wersji Pro ma skaner SQL injection
  • OWASP ZAP — darmowy skaner z wykrywaniem SQL injection

Lista kontrolna naprawy

  • Zastąp całe sklejanie ciągów w zapytaniach do bazy zapytaniami parametryzowanymi
  • Przejrzyj wszystkie wywołania surowego SQL w kodzie korzystającym z ORM
  • Zastosuj konta bazodanowe z najmniejszymi uprawnieniami
  • Wyłącz na produkcji wyświetlanie błędów bazy (błędy ujawniają informacje o schemacie)
  • Przejrzyj i przetestuj procedury składowane
  • Dodaj reguły WAF jako dodatkową warstwę
  • Przed każdym wydaniem uruchom sqlmap lub odpowiednik na środowisku testowym

SQL injection ma kompletne, dobrze znane rozwiązanie. Nie ma powodu, by pojawiał się w nowym kodzie. Jedyną barierą jest świadomość i dyscyplina podczas programowania.

#SQL injection#web security#OWASP#secure coding#databases