W budowie
Materiał edukacyjny · Informatyka

CZĘŚĆ 17

Bezpieczeństwo: SQL injection

PPrezentacja dla nauczyciela

Sklejenie zmiennych z formularza lub adresu URL bezpośrednio z tekstem zapytania SQL otwiera najgroźniejszą lukę: SQL Injection. Jedynym w 100% skutecznym i wymaganym na egzaminie INF.03 sposobem ochrony są zapytania przygotowane (prepared statements) w proceduralnym mysqli.

Jak działa SQL injection

SQL injection polega na dopisaniu fragmentu SQL do danych z formularza. Jeśli zapytanie powstaje przez sklejenie tekstu, użytkownik może zmienić jego sens.

Klasyczny przykład: pole login z wartością ' OR 1=1 -- zamyka apostrof i sprawia, że warunek staje się zawsze prawdziwy. Wtedy logowanie przechodzi bez hasła.

Skutki to nie tylko obejście logowania: odczyt innych tabel, zmiana danych, a przy błędnej konfiguracji nawet usunięcie tabel. Ryzyko rośnie, gdy konto bazy ma szerokie uprawnienia.

$login = $_POST['login'] ?? '';

// podatne: dane staja sie czescia zapytania
$sql = "SELECT id FROM users WHERE login = '$login'";

// bezpieczne: osobne zapytanie i osobne dane
$stmt = $pdo->prepare('SELECT id FROM users WHERE login = ?');
$stmt->execute([$login]);
1. Na czym polega SQL injection?
2. Który zapis jest podatny?
3. Co daje warunek OR 1=1?

Zapytania przygotowane

Zapytanie przygotowane rozdziela kod SQL od danych. Najpierw wysyłamy szkielet z pytajnikami, potem osobno wartości — baza nigdy nie potraktuje ich jak polecenia.

W PDO używamy prepare() i execute([...]). W mysqli jest mysqli_prepare() oraz wiązanie parametrów przez bind_param().

To podstawowe zabezpieczenie i jednocześnie wygodny sposób pracy: to samo zapytanie można wykonać wiele razy z różnymi danymi, bez budowania tekstu od nowa.

$stmt = $pdo->prepare('SELECT id, imie FROM uczniowie WHERE klasa = ? AND punkty > ?');
$stmt->execute(['3TI', 10]);

foreach ($stmt as $w) {
    echo htmlspecialchars($w['imie'], ENT_QUOTES, 'UTF-8');
}

// mysqli
$stmt2 = mysqli_prepare($polaczenie, 'SELECT imie FROM uczniowie WHERE klasa = ?');
mysqli_stmt_bind_param($stmt2, 's', $klasa);
mysqli_stmt_execute($stmt2);
4. Co robi prepare()?
5. Czym są pytajniki w SQL?
6. Co robi execute()?

Walidacja i uprawnienia bazy

Zapytania przygotowane chronią przed wstrzyknięciem, ale nie zastępują walidacji. Nadal sprawdzamy typ, zakres i długość danych, bo błędne wartości psują logikę aplikacji.

Drugi filar to minimalne uprawnienia konta bazy. Konto aplikacji powinno mieć prawo odczytu i zapisu w swoich tabelach, a nie prawo usuwania bazy.

Warto też wyłączać szczegółowe komunikaty błędów na produkcji. Komunikat z treścią zapytania ułatwia atakującemu poznanie struktury bazy.

$klasa = trim($_GET['klasa'] ?? '');
if (!preg_match('/^[1-4][A-Z]{2}$/', $klasa)) {
    exit('Nieprawidlowa klasa');
}

$stmt = $pdo->prepare('SELECT imie FROM uczniowie WHERE klasa = ?');
$stmt->execute([$klasa]);
$dane = $stmt->fetchAll();
7. Czy prepared statements wystarczą?
8. Jakie uprawnienia dla aplikacji?
9. Dlaczego ukrywamy błędy SQL?

Ćwiczenia