INF.03 · Aplikacje internetowe PHP
Podstawy bezpieczeństwa — SQL injection i zapytania przygotowane
Cztery granice, cztery zabezpieczenia.
- PHP8.3
- mysqli · MariaDB
- Praktyka i diagnoza
Po tej lekcji: Rozpoznasz SQLi, XSS, CSRF i dostęp do cudzych rekordów oraz dobierzesz właściwą ochronę.
Zrozum mechanizm
SQL injection pojawia się, gdy dane stają się składnią zapytania. Parametry prepare/bind_param/execute oddzielają wartości od SQL. Nie parametryzują nazw tabel lub kolumn — tam stosuj zamkniętą listę. Typowanie ID jest potrzebne, lecz nie zastępuje kontroli właściciela.
XSS dotyczy interpretacji tekstu jako HTML/skryptu. H() jest właściwe dla tekstu i atrybutów HTML, nie jest uniwersalnym kodowaniem dla JavaScript, CSS czy adresów URL. CSRF dotyczy niechcianej zmiany stanu z użyciem aktywnej sesji; POST sam jej nie eliminuje, dlatego sprawdzamy losowy token przez hash_equals. IDOR dotyczy cudzych obiektów — potrzebuje autoryzacji na serwerze.
Nie ćwicz ataków na bitedu.pl, cudzych serwisach ani kontach uczniów. Próby w tej lekcji wykonujesz wyłącznie na lokalnym fikcyjnym projekcie. Wyszukaj apostrof w katalogu i upewnij się, że pozostaje danymi, zapisz tytuł zawierający znaczniki i porównaj surową wartość w bazie z bezpiecznym wyświetleniem. Do testowania użyj własnej kopii bazy.
Jak uruchamiać krótkie przykłady?
Każdy krótki przykład oznaczony własnym plikiem uruchamiaj osobno, nie doklejaj wszystkich definicji do jednego pliku. W edytorze zapisz go jako .php, a w terminalu tego folderu wpisz np. php for.php. Wtedy pokazane końce wierszy PHP_EOL są widoczne. W odpowiedzi HTML znak końca wiersza sam nie robi nowej linii na ekranie: użyj <br> albo znacznika <pre>. Wyświetl źródło pozwala zobaczyć tekst odpowiedzi.
Przykłady opisane jako fragment projektu wymagają paczki KINO / LAB i wcześniejszej konfiguracji. Nie uruchamiaj ich jako samodzielnych endpointów. Pełny plik niżej pokazuje kontekst. Funkcje h(), pole() i wymagajPost() są nasze; ich definicje wczytuje bootstrap.php.
Kodowanie HTML i parametry SQL mają różne zadania
Ten sam tytuł może być zwykłym tekstem dla bazy, ale znacznikiem dla HTML. Zabezpieczenie dobierasz do miejsca użycia danych.
Podstawowa forma: htmlspecialchars(tekst, flagi, kodowanie)
<?php
$tytul = '<b>Ocean</b>';
echo htmlspecialchars($tytul, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
<b>Ocean</b>
- Pierwszy argument jest tekstem. ENT_QUOTES obejmuje oba rodzaje cudzysłowów, ENT_SUBSTITUTE zastępuje niepoprawne sekwencje kodowania.
|łączy flagi. - W źródle odpowiedzi pojawi się <b>, więc przeglądarka wyświetli dosłowne <b>Ocean</b>, a nie pogrubienie.
- h() w projekcie skraca właśnie to wywołanie. Nie używaj h jako zamiennika bind_param — HTML i SQL mają inne reguły.
Token formularza: wygeneruj, wyślij, porównaj
Token CSRF jest losową wartością przypisaną do sesji. Serwer dodaje ją do formularza i sprawdza po otrzymaniu POST. To nie hasło, ID konta ani klucz zamówienia.
Podstawowa forma: hash_equals(token_z_sesji, token_z_POST)
<?php
// Demonstracja porównania na fikcyjnych tekstach:
$tokenSesji = 'przykladowy-token';
$tokenFormularza = 'inny-token';
if (!hash_equals($tokenSesji, $tokenFormularza)) {
echo 'Odmowa: token nie pasuje';
}
Odmowa: token nie pasuje
- hash_equals porównuje dwa napisy w sposób przeznaczony do bezpiecznego porównania sekretów. ! odwraca wynik: odmawiamy, gdy nie pasują.
- Prawdziwy token w bootstrap powstaje przez
bin2hex(random_bytes(32)): 32 losowe bajty zamieniamy na 64 znaki zapisu szesnastkowego. Nie używaj stałego tekstu z demonstracji. - tokenFormularza() tworzy input hidden, a wymagajPost() odczytuje wartość i ją porównuje. Ukryte pole nie znaczy tajne — można je obejrzeć w HTML.
Jak czytać pozostałe zapisy w pełnym przykładzie
| Zapis | Znaczenie | Przykład użycia |
|---|---|---|
pole($dane, $nazwa) | Nasza funkcja: pobiera wskazany klucz tylko wtedy, gdy ma wartość string; inaczej oddaje pusty napis. isset sprawdza obecność i brak null, is_string typ. | pole($_POST, "email") |
h($tekst) | Nasza funkcja kodująca tekst/atrybut HTML przez htmlspecialchars; nie wykonuje walidacji zakresu ani zabezpieczenia SQL. | h($film["tytul"]) |
wymagajPost() | Nasza funkcja: sprawdza metodę POST oraz CSRF; przy błędzie przerywa odpowiedź z405/403. | wymagajPost(); |
wymagajLogowania() | Nasza funkcja: oddaje int ID konta z sesji lub przekierowuje do logowania. | $id = wymagajLogowania(); |
tokenFormularza() | Nasza funkcja zwraca HTML ukrytego pola csrf z wartością sesji zakodowaną przez h. | <?= tokenFormularza() ?> |
przekieruj($adres) | Nasza funkcja wysyła Location ze statusem303 i kończy wykonanie przez exit. Typ wyniku never oznacza, że nie wraca do dalszej instrukcji. | przekieruj("/panel.php"); |
pieniadze($grosze) | Nasza funkcja formatuje grosze jako złote. number_format przyjmuje liczbę, liczbę miejsc po przecinku, separator dziesiętny i separator grup. Wynik to string. | number_format(12.99, 2, ",", " ") |
$_SESSION i $_POST | Specjalne tablice PHP: pierwsza to stan sesji, druga dane formularza POST. Zewnętrzne wartości trzeba sprawdzić, nie ufać samym nazwom kluczy. | $_SESSION["user_id"] |
===, !==, &&, ||, ! | Identyczne, nieidentyczne, i, lub, zaprzeczenie. Porównanie ścisłe obejmuje typ; &&/|| nie sprawdzają drugiej części, gdy wynik jest już rozstrzygnięty. | $rok === false || $rok < 1888 |
(int), : int, : void, : never | (int) konwertuje wartość. Po dwukropku w definicji funkcji deklarujesz typ wyniku, nie wykonujesz konwersji: int liczba, void brak wyniku, never brak powrotu. | function id(): int { return 7; } |
Sprawdź w działaniu
Cztery granice, cztery zabezpieczenia — sprawdź w modelu
Model w przeglądarce wyjaśnia mechanizm na fikcyjnych danych. Nie wykonuje PHP ani zapytań SQL i nie tworzy kont. Prawdziwe skrypty uruchomisz lokalnie z paczki.
Przewidź rezultat, wykonaj próbę i wyjaśnij, która decyzja po stronie serwera decyduje o wyniku.
Uruchom krok po kroku
Prześledź w paczce cztery miejsca: przygotowane SELECT, h() w index.php, wymagajPost() z tokenem oraz WHERE id=? AND wlasciciel_id=? w CRUD. Do każdej próby zapisz oczekiwany status i brak niepożądanej zmiany w bazie. Nie usuwaj zabezpieczeń w działającej szkolnej platformie.
Funkcje ochrony i odpowiedzi
<?php
declare(strict_types=1);
function h(string $tekst): string
{
return htmlspecialchars($tekst, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function pole(array $dane, string $nazwa): string
{
// email[]=x jest tablicą, nie poprawnym tekstem. Nie rzutuj jej na string.
return isset($dane[$nazwa]) && is_string($dane[$nazwa]) ? $dane[$nazwa] : '';
}
function przekieruj(string $adres): never
{
header('Location: '.$adres, true, 303);
exit;
}
function wymagajPost(): void
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Allow: POST');
http_response_code(405);
exit('Ta operacja wymaga POST.');
}
$token = pole($_POST, 'csrf');
if ($token === '' || ! hash_equals($_SESSION['csrf'], $token)) {
http_response_code(403);
exit('Nieprawidłowy token formularza. Odśwież stronę.');
}
}
function wymagajLogowania(): int
{
if (! isset($_SESSION['user_id'])) {
przekieruj('/logowanie.php');
}
return (int) $_SESSION['user_id'];
}
function tokenFormularza(): string
{
return '<input type="hidden" name="csrf" value="'.h($_SESSION['csrf']).'">';
}
function pieniadze(int $grosze): string
{
return number_format($grosze / 100, 2, ',', ' ').' zł';
}
Pobierz i odtwórz projekt
W „Źródłach i materiałach” na końcu pobierz KINO / LAB.zip. Rozpakuj całość; instrukcja START.txt opisuje konfigurację. Pierwsze 6 przykładów w public/podstawy działa bez bazy. Dalsze wymagają lokalnej MariaDB 11.4, importu schema.sql i lokalnego konta bazy z minimalnymi prawami. Gotowa paczka pokazuje całość, ale w tej lekcji wykonaj opisane kroki i własne zadanie.
Ćwiczenie z pomocą · samodzielna praca
Twoja próba — podpowiedzi otwieraj po kolei
Przygotuj 4 próby lokalne: apostrof w wyszukiwaniu, HTML w tytule, POST bez CSRF, cudzy ID.
Brak błęduSQL; HTML jako tekst;403 bez tokenu;404 dla cudzego rekordu.
? Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź
Odszukaj wskazany plik i zapisz przewidywany rezultat przed zmianą.
Krok 2 — pokaż podpowiedź
Zmień jedną rzecz. Sprawdź metodę żądania, dane i odpowiedź lub wynik programu.
Krok 3 — pokaż podpowiedź
Wykonaj próbę graniczną lub błędną. Porównaj także stan bazy lub sesji, nie tylko komunikat.
Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.
Podsumowanie
Co warto zapamiętać
- Rozpoznasz SQLi, XSS, CSRF i dostęp do cudzych rekordów oraz dobierzesz właściwą ochronę.
- Potrafisz wskazać w kodzie przyczynę wyniku i zaplanować próbę błędnych danych.
- Nie oddajesz haseł, pliku config.local.php ani wartości cookie.
Dalej na swoim koncie
Rozgrzewki, quiz i zadania są w biurku ucznia.
Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.
Zaloguj się →