Lekcja 10 – Prepared Statements w PHP i bezpieczeństwo aplikacji

Prepared Statements w PHP – wprowadzenie

Bezpieczeństwo aplikacji internetowych jest kluczowe, szczególnie jeśli chodzi o ochronę przed atakami typu SQL Injection. SQL Injection to jedna z najczęstszych technik ataków, gdzie złośliwe dane wejściowe są wstrzykiwane do zapytań SQL w celu przejęcia kontroli nad bazą danych. Aby temu zapobiec, używamy prepared statements, które są zarówno bezpieczne, jak i wydajne. W tej lekcji dowiesz się, czym są prepared statements, jak z nich korzystać w PHP oraz dlaczego są one niezbędne do tworzenia bezpiecznych aplikacji.

Czym są prepared statements?

Prepared statements to technika programistyczna, w której zapytanie SQL jest najpierw “przygotowywane” przez serwer bazy danych, a następnie wykonywane z przekazanymi wartościami. Wartości te są wstrzykiwane do zapytania w sposób bezpieczny, zapobiegając SQL Injection.

Prepared statements są wykonywane w dwóch krokach:

  1. Przygotowanie zapytania: SQL bez rzeczywistych wartości jest przesyłany do serwera bazy danych, a serwer analizuje, kompiluje i optymalizuje zapytanie.
  2. Wykonanie zapytania: Prawdziwe wartości są później przekazywane jako parametry, co zapewnia, że dane nie mogą zmienić struktury samego zapytania.

Dlaczego prepared statements są bezpieczne?

Główną zaletą prepared statements jest to, że oddzielają kod SQL od danych, co zapobiega wstrzyknięciu złośliwego kodu do zapytania. Zamiast wstawiać dane bezpośrednio do zapytania SQL, przekazujemy je jako oddzielne parametry, które są “bezpiecznie” dołączane do zapytania przez silnik bazy danych.

Przykład SQL Injection bez prepared statements:

<?php
  $imie = $_POST['imie'];
  $sql = "SELECT * FROM uzytkownicy WHERE imie = '$imie'";
  mysqli_query($conn, $sql);
?>

Jeśli użytkownik wprowadziłby coś w rodzaju Jan'; DROP TABLE uzytkownicy; --, zapytanie SQL zostałoby zmodyfikowane i mogłoby spowodować usunięcie całej tabeli.

Przy użyciu prepared statements, serwer bazy danych interpretuje wartości wejściowe jako dane, a nie jako część kodu SQL, co uniemożliwia taki atak.

Korzystanie z prepared statements w PHP (MySQLi)

PHP obsługuje prepared statements za pomocą rozszerzenia MySQLi oraz PDO. W tej lekcji skupimy się na MySQLi.

Tworzenie prepared statements z MySQLi

Krok 1: Połączenie z bazą danych

Najpierw nawiążmy połączenie z bazą danych.

<?php
  $conn = mysqli_connect("localhost", "root", "", "moja_baza");

  if (!$conn) {
    die("Połączenie nieudane: " . mysqli_connect_error());
  }
?>

Krok 2: Przygotowanie zapytania

Przygotowanie zapytania odbywa się przy użyciu funkcji mysqli_prepare(). Tworzymy szkielet zapytania SQL z miejscem na parametry (zastępstwa, np. ?).

<?php
  $stmt = mysqli_prepare($conn, "SELECT * FROM uzytkownicy WHERE imie = ?");
?>

Krok 3: Wiązanie parametrów

Następnie przypisujemy wartości do parametrów zapytania za pomocą funkcji mysqli_stmt_bind_param(). Używamy odpowiednich typów danych:

  • s: ciąg znaków (string),
  • i: liczba całkowita (integer),
  • d: liczba zmiennoprzecinkowa (double),
  • b: dane binarne (blob).

Przykład wiązania parametru dla zapytania, gdzie $imie to ciąg znaków:

<?php
  $imie = $_POST['imie'];
  mysqli_stmt_bind_param($stmt, "s", $imie);
?>

Krok 4: Wykonanie zapytania

Po przypisaniu wartości, wykonujemy zapytanie za pomocą funkcji mysqli_stmt_execute().

<?php
  mysqli_stmt_execute($stmt);
?>

Krok 5: Pobieranie wyników

Jeśli zapytanie zwraca dane (np. SELECT), musimy pobrać wyniki. Możemy to zrobić za pomocą funkcji mysqli_stmt_bind_result(), a następnie odczytać dane za pomocą mysqli_stmt_fetch().

<?php
  mysqli_stmt_bind_result($stmt, $id, $imie, $email, $data_rejestracji);

  while (mysqli_stmt_fetch($stmt)) {
    echo "ID: $id, Imię: $imie, Email: $email, Data rejestracji: $data_rejestracji<br>";
  }
?>

Krok 6: Zamknięcie zapytania

Po zakończeniu pracy z zapytaniem musimy je zamknąć, aby zwolnić zasoby serwera.

<?php
  mysqli_stmt_close($stmt);
?>

Kompleksowy przykład użycia prepared statements

Oto pełny przykład użycia prepared statements, które odczytuje dane użytkownika z tabeli na podstawie przekazanego imienia.

<?php
  // Połączenie z bazą danych
  $conn = mysqli_connect("localhost", "root", "", "moja_baza");

  // Sprawdzanie połączenia
  if (!$conn) {
    die("Połączenie nieudane: " . mysqli_connect_error());
  }

  // Przygotowanie zapytania
  $stmt = mysqli_prepare($conn, "SELECT id, imie, email, data_rejestracji FROM uzytkownicy WHERE imie = ?");

  // Przypisywanie parametru
  $imie = $_POST['imie'];
  mysqli_stmt_bind_param($stmt, "s", $imie);

  // Wykonanie zapytania
  mysqli_stmt_execute($stmt);

  // Przypisywanie wyników do zmiennych
  mysqli_stmt_bind_result($stmt, $id, $imie, $email, $data_rejestracji);

  // Pobieranie i wyświetlanie wyników
  while (mysqli_stmt_fetch($stmt)) {
    echo "ID: $id, Imię: $imie, Email: $email, Data rejestracji: $data_rejestracji<br>";
  }

  // Zamknięcie zapytania i połączenia
  mysqli_stmt_close($stmt);
  mysqli_close($conn);
?>

Prepared statements z wieloma parametrami

Prepared statements obsługują również wiele parametrów. Oto przykład, gdzie używamy dwóch parametrów — imienia i adresu e-mail:

<?php
  // Połączenie z bazą danych
  $conn = mysqli_connect("localhost", "root", "", "moja_baza");

  // Sprawdzanie połączenia
  if (!$conn) {
    die("Połączenie nieudane: " . mysqli_connect_error());
  }

  // Przygotowanie zapytania
  $stmt = mysqli_prepare($conn, "SELECT id, imie, email, data_rejestracji FROM uzytkownicy WHERE imie = ? AND email = ?");

  // Przypisywanie parametrów
  $imie = $_POST['imie'];
  $email = $_POST['email'];
  mysqli_stmt_bind_param($stmt, "ss", $imie, $email);

  // Wykonanie zapytania
  mysqli_stmt_execute($stmt);

  // Przypisywanie wyników do zmiennych
  mysqli_stmt_bind_result($stmt, $id, $imie, $email, $data_rejestracji);

  // Pobieranie i wyświetlanie wyników
  while (mysqli_stmt_fetch($stmt)) {
    echo "ID: $id, Imię: $imie, Email: $email, Data rejestracji: $data_rejestracji<br>";
  }

  // Zamknięcie zapytania i połączenia
  mysqli_stmt_close($stmt);
  mysqli_close($conn);
?>

Korzyści z użycia prepared statements

  1. Bezpieczeństwo: Prepared statements skutecznie chronią przed atakami SQL Injection, ponieważ parametry są bezpiecznie przekazywane do zapytania.
  2. Wydajność: Prepared statements są bardziej wydajne, ponieważ zapytanie SQL jest analizowane i optymalizowane tylko raz, nawet jeśli jest wielokrotnie wykonywane z różnymi wartościami.
  3. Czytelność: Rozdzielenie logiki SQL od danych sprawia, że kod staje się bardziej przejrzysty i łatwiejszy do zarządzania.

Podsumowanie

W tej lekcji dowiedzieliśmy się, czym są prepared statements w PHP oraz dlaczego są one kluczowe dla bezpieczeństwa aplikacji internetowych. Prepared statements chronią przed SQL Injection i zwiększają wydajność aplikacji poprzez oddzielanie danych od zapytań SQL. To niezbędne narzędzie dla każdego programisty pracującego z bazami danych.

Gratulacje! Ukończyłeś lekcję 10.
Przejdź teraz do lekcji 11 >> Zarządzanie użytkownikami – rejestracja, logowanie i autoryzacja w PHP


Spis Treści - darmowy kurs PHP

Wprowadzenie: Instalacja środowiska PHP
Lekcja 1: Podstawy składni PHP
Lekcja 2: Funkcje i instrukcje warunkowe w PHP
Lekcja 3: Pętle w PHP
Lekcja 4: Tablice w PHP
Lekcja 5: Dodatkowe podstawy funkcji w PHP
Lekcja 6: Praca z formularzami HTML w PHP
Lekcja 7: Obsługa plików w PHP
Lekcja 8: Sesje i ciasteczka w PHP
Lekcja 9: Podstawy operacji na bazach danych MySQL z PHP
Lekcja 10: Prepared Statements w PHP i bezpieczeństwo aplikacji
Lekcja 11: Zarządzanie użytkownikami – rejestracja, logowanie i autoryzacja w PHP
Lekcja 12: Wzorce projektowe w PHP – wprowadzenie do wzorca MVC
Lekcja 13: Zaawansowane techniki pracy z bazami danych w PHP
Lekcja 14: Testowanie jednostkowe w PHP z PHPUnit
Lekcja 15: Tworzenie i korzystanie z API RESTful w PHP
Lekcja 16: Obsługa plików JSON i XML w PHP

Dodatki
- Spis najważniejszych funkcji PHP