Przejdź do treści

SQL injection w PHP 8 – Metody zabezpieczeń

Krótko i na temat, jak się zabezpieczyć przed SQL injection w PHP. Zacznijmy od tego czym jest SQL injection. Najprościej opisujac atak polegający na wykorzystywaniu przez przestępców luk występujących w zabezpieczeniach np. aplikacji i pozwalający na uzyskanie przez osoby nieuprawnione możliwości kradzieży, modyfikacji lub zniszczenia danych.

Dla PHP, przykładowy kod ataku SQL injection (który podaje dla celów edukacyjnych) to:

[...]
//Skrypt ma pobrać wszystkie dane konkretnego użytkownika
$user = $_GET['user']; //Pobieramy użytkownika z GET np. profile.php?user=admin
$query = "SELECT * FROM users WHERE username = '$user'"; //Pobieram wszystkie dane dla użytkownika który ma nazwe admin (dla tego przykładu)
$result = $db->query($query); //Wyświetla wynik zapytania

//Wynik jest następujący, pobierze dane tylko dla username który będzie równy wartości $user, w praktyce precyzyjne zapytanie

/*
* A teraz co jeśli ktoś zamiast 1 do adresu wklei ' OR '1'='1
* Zapytanie nie będzie wyglądać 
* 1)SELECT * FROM users WHERE username = 'admin'
* Tylko tak jak poniżej:
* 2)SELECT * FROM users WHERE username = 'admin' OR '1'='1'
* 
* W 1) przypadku, pobierze tylko użytkownika spełniającego warunek username = 'admin' 
* Za to w 2) przypadku pobiera wszystko :) 
* 
*/

Dlatego jest to bardzo niebezpieczne. Bo bardzo łatwo można dodać dodatkowe zapytanie, przykładowo DROP, albo pobrać dane z wszystkich innych tabel, jedna luka wiele szkód.

Jak tego uniknąć? Po pierwsze nie ufaj użytkownikowi, po drugie jako deweloper zawsze kilka razy sprawdź zapytanie.

Dobre praktyki jak uniknąć SQL injection i tego typu sytuacji:

A) Unikaj dodawania pobranych wartości bezpośrednio do zapytani i łączenia.

//Przykład niefiltrowanego zapytania z wartością bezpośrednio przekazywaną do zapytania
$query = "INSERT INTO posts (title) VALUES ('" . $_POST['title'] . "')";
//Tak nie róbmy

B) Filtruj dane wejściowe

//Proste filtrowanie GET
$id = filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT);
if ($id === false) {
    die("Nieprawidłowy parametr!");
}

C) Sprawdzaj dokumentację i aktualizuj wersję PHP. Metod zabezpieczeń jest wiele a czasem zwykła aktualizacja i dokumentacji może pomóc.

/* Prosty przykład od wersji 8.2 nie wolno używać: FILTER_SANITIZE_STRING;
* Już od wersji 7.4 miało status deprecated
* Dlaczego usunięto?
* a) Jeśli był źle wykorzystany, można było go wykorzystać do ataków;
* b) Filtrował dane, ale bez pewności że są poprawne;
* 
* W nowoczesnych edytorach takich jak PHP Storm program informuje o takich zmianach;
* 
* Więc zamiast korzystać:
* $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
* Skorzystaj z:
* $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_SPECIAL_CHARS);
* Co wynika bezpośrednio z dokumentacji i wersji PHP która poprawia takie "kwiatki"
* 
* Aby dobrze zabezpieczyć się przed SQL injection można dodatkowo dla najwrażliwszych 
* miejsc jeśli to możliwe stosować Regex, i funkcję preg_match('REGEX',$zmienna');
* /

D) Ogranicz uprawnienia użytkownika w bazie danych

/*
* Jeśli nie jest to wskazane (a najczęściej nie jest),
* zabierz użytkownikom uprawnienia do:
* DROP
* ALERT
* GRANT
*/

E) Nie wyświetlaj błędów użytkownikom

//Prosty kod i użytkownic nic nie widzą
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');

//Dodatkowo można dodać zabezpieczanie w php.ini lub .htaccess
display_errors = Off
display_startup_errors = Off

//Warto w tym wypadku dodać raportowanie błędów do pliku,
//aby mieć jakąkolwiek diagnostykę w przypadku błędów

error_reporting(E_ALL);
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/logs/php-error.log');

F) Jeśli wiesz że dane zapytanie ma zwrócić konkretną ilość wyników, pamiętaj o LIMIT

// Zakładamy że chcemy pobrać dane tylko dla jednego użytkownika
// Więc po pierwsze do zapytania dod :)
SELECT * FROM users WHERE username = 'admin' LIMIT 1

Napisałem kilka metod dzięki który korzystając z języka PHP uczynimy kod „trochę” bezpieczniejszym

//Posłużę się zapytaniem z edukacyjnego przykładu, jak już wiemy poniży kod nie 
//jest zabezpieczony przed SQL injection;
//jak mogło by wyglądać przykładowe zabezpieczenie w PHP?

$user = $_GET['user']; 
$query = "SELECT * FROM users WHERE username = '$user'"; 
$result = $db->query($query);

//Jak zrobić to bezpiecznie?

//Wyłączymy raportowanie o błędach przykładowo w pliku config.php
[config.php]
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');

//Najpierw filtrujemy GET
$user = filter_input(INPUT_GET, 'username', FILTER_UNSAFE_RAW);

//Dodatkowo zabezpieczenie z wykorzystaniem REGEX
//Username musi mieć między 4 a 64 znaki 
if (strlen($username) < 4 || strlen($username) > 64) {
    die('Zwracam błąd.');
}

//Dodatkowo zabezpieczenie z wykorzystaniem REGEX
//Tylko litery, liczby, i jeszcze _
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
    die('Zwracam błąd.');
}

$query = "SELECT * FROM users WHERE username = '$user' LIMIT 1"; 
$result = $db->query($query);

//Już jest lepiej, zawsze i najlepiej skorzystać jeszcze z 
//Prepared Statements dla (PDO)

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(":username", $user, PDO::PARAM_STR);
$stmt->execute();

//Ale o PDO stworzę oddzielny poradnik

Podsumowanie wiedzy na temat SQL injection w PHP tego jak się zabezpieczyć

Po pierwsze, to tylko kilka przykładów, będę starał się stale aktualizować listę, aby aktualizować różne metody zabezpieczenia przed SQL injection.

Po drugie, zawsze warto zajrzeć do dokumentacji przykładowo: https://www.php.net/manual/en/security.database.sql-injection.php

Ostatnia aktualizacja: 19.11.2025