SQL injection to podatność, w której dane wprowadzone przez użytkownika trafiają do zapytania bazy danych jako fragment kodu SQL, a nie jako zwykła wartość. Atakujący może wtedy odczytać, zmienić lub usunąć rekordy, a niekiedy przejąć całą bazę. Obrona sprowadza się do rozdzielenia kodu od danych za pomocą zapytań parametryzowanych i walidacji tam, gdzie parametru użyć się nie da.
- Na czym polega — aplikacja skleja zapytanie z tekstu użytkownika, a baza traktuje ten tekst jako polecenie (CWE-89, MITRE).
- Jak rozpoznać w kodzie — wartość od użytkownika doklejana do zapytania przez konkatenację ciągów zamiast jako parametr.
- Główna obrona — prepared statements z parametryzacją, tak by baza zawsze odróżniała kod od danych (OWASP).
- Czego nie da się parametryzować — nazw tabel, kolumn i kierunku sortowania; tu chroni tylko walidacja allow-list.
Jak działa atak SQL injection
Atak SQL injection działa wtedy, gdy aplikacja buduje zapytanie SQL przez sklejanie tekstu, w którym część pochodzi od użytkownika, a silnik bazy interpretuje ten tekst jako polecenie, nie jako wartość. MITRE opisuje ten mechanizm jako CWE-89: produkt tworzy zapytanie z danych pochodzących z zewnątrz i nie neutralizuje znaków specjalnych, przez co dane użytkownika mogą zostać zinterpretowane jako SQL, a nie jak zwykłe dane (MITRE, CWE-89).
Skala problemu pozostaje wysoka. W zestawieniu 2025 CWE Top 25 Most Dangerous Software Weaknesses CWE-89 zajmuje 2. miejsce z wynikiem 28,72, o jedną pozycję wyżej niż rok wcześniej, gdzie było 3. (MITRE, 2025). W OWASP Top 10:2025 kategoria Injection figuruje jako A05:2025 i obejmuje 37 zmapowanych CWE oraz 1 404 249 wystąpień w 62 445 CVE (OWASP, 2025). Więcej o samej klasyfikacji znajdziesz w opisie listy kategorii ryzyka OWASP Top 10 w praktyce.
Konsekwencje bywają dotkliwe także w gotowym oprogramowaniu. Podatność CVE-2023-34362 w MOVEit Transfer, sklasyfikowana jako CWE-89 z oceną CVSS 9.8, pozwalała nieuwierzytelnionemu atakującemu sięgnąć do bazy danych i trafiła do katalogu CISA KEV w dniu publikacji, 2 czerwca 2023 (NVD). CISA i FBI zaliczają SQLi do błędów, które inni od 2007 roku nazywają „niewybaczalnymi” (CISA i FBI, 2024).
Jak wygląda podatny kod i jak wygląda payload
Podatny kod poznaje się po tym, że wartość od użytkownika jest doklejana do zapytania przez konkatenację ciągów, zamiast trafiać do niego jako osobny parametr. Poniżej ten sam błąd w dwóch językach.
// PHP - podatny wzorzec
$name = $_GET['name'];
$sql = "SELECT * FROM users WHERE name = '" . $name . "'";
$result = $conn->query($sql);
# Python - podatny wzorzec
name = request.args.get("name")
cur.execute("SELECT * FROM users WHERE name = '" + name + "'")
Wystarczy, że użytkownik poda wartość zawierającą apostrof, aby zmienić strukturę zapytania. Payload ' OR '1'='1 w polu logowania rozszerza warunek tak, że pasuje do każdego rekordu, a Kowalski'-- (w MySQL po myślnikach komentarza musi wystąpić spacja) zamyka ciąg i komentuje resztę zapytania, omijając na przykład sprawdzenie hasła. Do wyciągania danych z innych tabel atakujący używa konstrukcji UNION SELECT ..., dopasowując liczbę i typy kolumn do oryginalnego zapytania.
Jakie rodzaje SQL injection widać w testach
W testach powtarza się kilka rodzajów SQL injection, opisanych też w OWASP Web Security Testing Guide (WSTG-INPV-05). Różnią się tym, jak atakujący odbiera odpowiedź z bazy.
| Rodzaj | Jak się objawia |
|---|---|
| In-band: UNION-based | Dane z innych tabel dołączane wprost do wyniku widocznego w odpowiedzi aplikacji. |
| In-band: error-based | Informacje wyciekają przez treść komunikatów o błędach bazy zwracanych użytkownikowi. |
| Blind: boolean-based | Brak danych w odpowiedzi; różnicę widać po zmianie zachowania dla ' OR '1'='1 kontra ' AND '1'='2. |
| Blind: time-based | Wniosek o prawdziwości warunku wyciąga się z opóźnienia odpowiedzi wymuszonego funkcją usypiającą. |
| Out-of-band | Baza wysyła dane osobnym kanałem, na przykład zapytaniem DNS lub HTTP do serwera atakującego. |
Jak bronić się przed SQL injection
Przed SQL injection broni się przede wszystkim rozdzieleniem kodu od danych, czyli zapytaniami parametryzowanymi. OWASP SQL Injection Prevention Cheat Sheet porządkuje obrony w czterech krokach, uzupełnionych zasadą najmniejszych uprawnień.
- Opcja 1: prepared statements z parametryzacją. Wartości przekazuje się jako parametry, dzięki czemu, jak ujmuje to OWASP, „baza zawsze odróżni kod od danych, niezależnie od tego, co poda użytkownik”. CISA i FBI wskazują je jako podejście secure by design (CISA i FBI, 2024).
- Opcja 2: procedury składowane. Bezpieczne, o ile same budują zapytania parametrycznie, a nie przez sklejanie tekstu w środku procedury.
- Opcja 3: walidacja allow-list. Dopuszczasz wyłącznie z góry znaną listę wartości. Konieczna tam, gdzie parametr nie zadziała.
- Opcja 4: escaping (zdecydowanie odradzany). Kruchy i trudny do wyegzekwowania na skalę, łatwy do obejścia (CISA i FBI, 2024). Traktuj jako ostateczność, nie jako podstawę.
- Least privilege. Konto aplikacji dostaje tylko uprawnienia, których naprawdę używa, co ogranicza skutki udanego ataku.
Tak wygląda poprawiony wariant z wcześniejszego przykładu: wartość trafia do zapytania jako parametr.
// PHP PDO - prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute([$name]);
# Python (psycopg2 / mysql-connector) - zapytanie parametryzowane
cur.execute("SELECT * FROM users WHERE name = %s", (name,))
# w sqlite3 placeholderem jest ?, nie %s
Nie wszystko da się jednak związać parametrem. OWASP zaznacza, że dla fragmentów zapytań, które nie mogą korzystać ze zmiennych wiązanych, jak nazwy tabel, nazwy kolumn czy wskaźnik sortowania (ASC lub DESC), najwłaściwszą obroną jest walidacja wejścia lub przeprojektowanie zapytania. W praktyce oznacza to mapowanie wejścia na sztywną listę dozwolonych kolumn przed złożeniem klauzuli ORDER BY.

Gdzie SQL injection kryje się najczęściej
Podatność najczęściej nie siedzi w oczywistym formularzu logowania, lecz w parametrach sortowania i filtrowania list. W projektach, które prowadzimy, najczęściej trafiamy na SQLi właśnie w nazwie kolumny przekazywanej do ORDER BY albo w polu filtra, bo tych miejsc nie chroni prosta parametryzacja i programiści o nich zapominają. Stąd proste kryterium decyzyjne: jeśli fragment zapytania nie może być parametrem (nazwa kolumny, tabeli albo kierunek sortowania), musi przejść przez allow-listę dozwolonych wartości, nigdy przez escaping. Zdarza się to nawet w dojrzałych systemach: podatność CVE-2025-25257 w Fortinet FortiWeb (CWE-89, CVSS 9.8) trafiła do katalogu CISA KEV 18 lipca 2025 (NVD), a raport CERT Polska opisuje CVE-2023-48788 w FortiClientEMS, gdzie nieuwierzytelniony atakujący wstrzykiwał zapytania wprost do bazy Microsoft SQL Server (CERT Polska, 2025).
Najczęstsze pytania o SQL injection
Czy ORM (np. Hibernate, Entity Framework) chroni przed SQL injection?
Zwykle tak, dopóki korzystasz z jego zapytań parametrycznych, bo pod spodem generuje prepared statements. Ochrona znika, gdy sklejasz surowy SQL lub fragmenty HQL/JPQL z tekstu użytkownika albo używasz natywnych zapytań z konkatenacją. ORM to narzędzie, nie gwarancja: parametryzacja musi zostać zachowana także w miejscach, gdzie omijasz warstwę mapowania.
Czy filtrowanie apostrofu wystarczy, żeby się zabezpieczyć?
Nie. Filtrowanie apostrofu to escaping, który OWASP stawia jako opcję ostatnią i zdecydowanie odradza, a CISA i FBI opisują takie techniki jako kruche, trudne do egzekwowania na skalę i częste do obejścia (CISA i FBI, 2024). Ataki w kontekstach liczbowych czy przez kodowanie nie potrzebują apostrofu. Podstawą powinny być prepared statements.
Czym różni się blind SQL injection od klasycznego?
W klasycznym in-band atakujący widzi dane wprost w odpowiedzi aplikacji, na przykład dołączone przez UNION albo w treści błędu. W wariancie blind aplikacja nic nie zwraca, więc informacje wyciąga się pośrednio: po zmianie zachowania strony dla różnych warunków (boolean-based) albo po czasie odpowiedzi wymuszonym opóźnieniem (time-based). Blind jest wolniejszy, ale równie skuteczny.
Czy stored procedures zawsze są bezpieczne?
Nie zawsze. OWASP wymienia procedury składowane jako obronę numer dwa, ale tylko wtedy, gdy same budują zapytania parametrycznie. Jeśli wewnątrz procedury dynamiczny SQL powstaje przez sklejanie przekazanych parametrów w tekst i wykonanie go, podatność wraca. Bezpieczeństwo daje parametryzacja, a nie samo opakowanie logiki w procedurę.
Jak Pentestica może pomóc
Weryfikujemy podatności SQL injection w praktyce, testując aplikacje ręcznie zgodnie z OWASP Web Security Testing Guide (WSTG-INPV-05), a nie tylko skanerem. Sprawdzamy również miejsca, o których łatwo zapomnieć, jak sortowanie i filtrowanie list. Mamy ponad 10 lat doświadczenia i setki zrealizowanych projektów. Zobacz usługę testów penetracyjnych aplikacji albo od razu określ zakres w konfiguratorze zakresu badania. Jeśli chcesz poznać sąsiednią kategorię injection, przeczytaj też, czym jest cross-site scripting (XSS).
Źródła
- CWE-89, MITRE — definicja SQL injection i mechanizmu neutralizacji znaków specjalnych.
- 2025 CWE Top 25 Most Dangerous Software Weaknesses, MITRE — pozycja i wynik punktowy CWE-89.
- OWASP Top 10:2025, A05 Injection — metryki kategorii i warunki podatności.
- OWASP SQL Injection Prevention Cheat Sheet — cztery obrony pierwotne i least privilege.
- OWASP Query Parameterization Cheat Sheet — przykłady prepared statements w wielu językach.
- OWASP WSTG-INPV-05, Testing for SQL Injection — techniki testowania SQLi.
- CISA i FBI, Secure by Design Alert (2024) — rekomendacja parametryzacji i ocena sanityzacji.
- CERT Polska, Raport roczny 2024 (NASK PIB, 2025) — opis CVE-2023-48788 w FortiClientEMS, s. 25.
- CVE-2023-34362, NVD — SQL injection w MOVEit Transfer, CVSS 9.8.
- CVE-2025-25257, NVD — SQL injection w Fortinet FortiWeb, CVSS 9.8.

Redakcja Pentestica to zespół pentesterów i konsultantów bezpieczeństwa, którzy na co dzień wykonują testy penetracyjne, testy TLPT wymagane przez DORA, red teaming i audyty bezpieczeństwa IT, a także prowadzą wdrożenia zgodności z NIS2, DORA i MiCA. Teksty na tym blogu piszemy z tej praktyki, a nie z opracowań. Każde twierdzenie o przepisie, terminie albo karze opieramy na źródle pierwotnym: dzienniku ustaw, tekście dyrektywy, komunikacie organu nadzoru. Jeśli czegoś nie da się potwierdzić, piszemy o tym wprost, zamiast powtarzać liczbę krążącą po branżowych portalach.