zapytanie parametryzowane
| Po angielsku | prepared statement |
|---|---|
| Którą formą pisać | po polsku Polska nazwa przyjęła się w tekstach i w wyszukiwarce — używaj jej samodzielnie. |
| Inne formy | przygotowane zapytanie · zapytanie sparametryzowane |
| Kategoria | Strony WWW, WordPress i hosting |
Zapytanie parametryzowane to zapytanie SQL, w którym treść polecenia i dane użytkownika trafiają do bazy osobno. Wartość z formularza zajmuje w nim miejsce parametru i nie może zmienić struktury polecenia. OWASP wymienia zapytania przygotowane z wiązaniem zmiennych jako pierwszą z podstawowych obron przed SQL injection [1]. W angielskiej literaturze to prepared statement albo parameterized query; CERT Polska pisze o przygotowanych zapytaniach [7].
Jak działa
Podatność SQL injection powstaje, gdy program buduje polecenie SQL z danych z zewnątrz i nie neutralizuje elementów, które mogą zmienić jego sens [2]. W zestawieniu CWE Top 25 za 2024 rok słabość CWE-89 zajęła trzecie miejsce [3]; zob. CWE.
Zapytanie parametryzowane rozwiązuje problem u źródła. Program najpierw wysyła szablon z miejscami na dane, potem same wartości. OWASP tłumaczy, że baza zawsze odróżni wtedy kod od danych, niezależnie od tego, co wpisał użytkownik; ciąg tom' or '1'='1 zostanie potraktowany jako zwykły tekst [1]. Przebieg pokazuje schemat poniżej.
W PHP zapytania przygotowane udostępnia PDO. Dokumentacja PHP podaje dwie korzyści: zapytanie jest analizowane raz i można je wykonać wiele razy z różnymi parametrami, a parametrów nie trzeba cytować, bo robi to sterownik [4]. Jeśli aplikacja używa wyłącznie zapytań przygotowanych, SQL injection nie wystąpi, chyba że inne części zapytania są sklejane z niezabezpieczonych danych [4].
$st = $pdo->prepare( 'SELECT id FROM users WHERE email = :email' );
$st->execute( [ 'email' => $_POST['email'] ] );
Odmiany obrony i ich miejsce
OWASP porządkuje obrony przed SQL injection w czterech punktach [1].
| Obrona | Ocena OWASP | Uwagi |
|---|---|---|
| zapytania przygotowane | podstawowa [1] | domyślny wybór |
| procedury składowane | dopuszczalne przy poprawnej budowie [1] | bez dynamicznego SQL w środku [2] |
| walidacja z listą dozwolonych wartości | podstawowa [1] | dla nazw kolumn i kierunku sortowania |
| escapowanie wszystkich danych | zdecydowanie odradzane [1] | łatwo o pomyłkę |
MITRE zaleca przetwarzanie zapytań przez zapytania przygotowane, parametryzowane albo procedury składowane z silnym typowaniem [2]. Ostrzega przy tym, że procedura, która wewnątrz składa i wykonuje zapytanie z tekstu, na nowo otwiera drogę do wstrzyknięcia [2].
Parametrem nie da się zastąpić każdego fragmentu polecenia. Nazwy tabel i kolumn oraz kierunek sortowania ASC albo DESC nie przyjmują wiązania; OWASP zaleca dla nich walidację wejścia albo przebudowę zapytania, na przykład zamianę wyboru użytkownika na wartość logiczną i doklejenie gotowego słowa kluczowego [1]. Walidację opisuje hasło sanityzacja danych wejściowych.
W WordPressie
WordPress ma własną klasę dostępu do bazy, $wpdb, i metodę $wpdb->prepare(). Dokumentacja opisuje ją jako przygotowanie zapytania SQL do bezpiecznego wykonania w składni podobnej do sprintf() [5]. Obsługuje symbole zastępcze %s dla tekstu, %d dla liczb całkowitych, %f dla liczb zmiennoprzecinkowych i od wersji 6.2.0 %i dla identyfikatorów, czyli nazw tabel i pól [5]. Symboli nie wolno ujmować w cudzysłowy, a dane użytkownika mają trafiać wyłącznie przez parametry, nigdy do samego szablonu [5].
$wpdb->get_results(
$wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_author = %d", $author_id )
);
To nie jest zapytanie przygotowane w sensie bazy danych. Sekurak zwraca uwagę, że prepare() działa na zasadzie dopasuj i zastąp: w miejsce %s wstawia wartość zabezpieczoną po stronie PHP [6]. Ma to praktyczne skutki. W 2017 roku WordPress 4.8.3 poprawiał podatność typu podwójne przygotowanie zapytania, gdy dane użytkownika zawierające %s przechodziły przez prepare() drugi raz; poprzednia próba poprawki w wersji 4.8.2 okazała się niewystarczająca [6]. Stąd rada Sekuraka, by nie przekazywać do prepare() jako szablonu żadnych wartości pochodzących od użytkownika [6].
Błędy SQL injection w WordPressie dotyczą głównie wtyczek, które sklejają zapytania z $_GET albo $_POST; zob. podatność we wtyczce. Szczególnie groźna jest podatność wykorzystywana bez uwierzytelnienia, bo nie wymaga konta na stronie. Wariant, w którym odpowiedź nie zawiera danych, to blind SQL injection.
Typowy błąd we wtyczce wygląda tak: wartość z adresu trafia wprost do szablonu, a prepare() jest wywołane bez parametrów albo wcale. Poprawna wersja przekazuje ją jako osobny argument z odpowiednim symbolem zastępczym [5].
// podatne: dane sklejone z szablonem
$wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE post_name = '" . $_GET['slug'] . "'" );
// poprawne: dane jako parametr %s
$wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE post_name = %s", $_GET['slug'] ) );
W pierwszej linii apostrof w parametrze slug kończy ciąg znaków i pozwala dopisać dowolny warunek. W drugiej ten sam apostrof zostaje zabezpieczony i porównany z nazwą wpisu jak zwykły tekst.
Zagrożenia przy braku parametryzacji
Brak zapytań przygotowanych daje atakującemu wpływ na treść polecenia SQL. CERT Polska 30 czerwca 2026 roku opisał CVE-2026-53690 w systemie Redeight CMS: aplikacja umieszczała dane wejściowe w zapytaniach bez przygotowanych zapytań, co pozwalało nieuwierzytelnionemu atakującemu wykonać dowolne polecenia SQL [7]. Ten sam komunikat opisuje hasła przechowywane jako MD5 bez soli, możliwe do odtworzenia tęczowymi tablicami [7]. Połączenie obu błędów oznacza, że wykradzione z bazy hasła szybko stają się danymi uwierzytelniającymi do kont.
Dobre praktyki
- Każde zapytanie z danymi z zewnątrz budować jako przygotowane: PDO lub mysqli z wiązaniem parametrów, w WordPressie
$wpdb->prepare()z symbolami zastępczymi [1] [5]. - Nazwy kolumn i kierunek sortowania wybierać z listy dozwolonych wartości [1].
- escapowanie traktować jako uzupełnienie dla wyjścia HTML, a nie obronę przed SQL injection [1].
- Konto bazy ograniczać zgodnie z zasadą najmniejszych uprawnień, aby udane wstrzyknięcie miało mniejszy zasięg.
- Tam, gdzie kodu nie da się szybko poprawić, stosować wirtualne patchowanie (virtual patching) w WAF jako rozwiązanie przejściowe.
Przypisy
7- [1] SQL Injection Prevention Cheat Sheet · OWASP
- [2] CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') · MITRE
- [3] 2024 CWE Top 25 Most Dangerous Software Weaknesses · MITRE
- [4] Prepared statements and stored procedures — PHP Manual · PHP Group
- [5] wpdb::prepare() — Code Reference · WordPress.org
- [6] WordPress wypuszcza aktualizację – SQL injection · Sekurak
- [7] Podatności w oprogramowaniu Redeight CMS (CVE-2026-53690, CVE-2026-53691, CVE-2026-53692) · CERT Polska