Gdy strona wypisuje tekst wpisany przez użytkownika bez zabezpieczenia, atakujący może podstawić własny kod HTML lub JavaScript — to atak XSS. Skutecznym i prostym zabezpieczeniem jest zamiana znaków specjalnych na encje HTML w miejscu wypisywania. Na lekcji uczniowie widzą różnicę między wypisaniem „na surowo” a przez htmlspecialchars i uczą się pułapki z atrybutami.
Scenariusz jest przeznaczony na 45 minut dla klasy znającej zmienne, funkcje i formularze w PHP — na przykład po lekcji o formularzach PHP. Ćwiczenia pokazują atak w bezpiecznym środowisku, na tekście wypisywanym w terminalu.
Przygotowanie stanowisk
Potrzebny jest PHP 8.x uruchamiany w terminalu (na przykład z pakietu XAMPP; sprawdź polecenie php -v). Rozpakuj pakiet z sekcji materiałów do osobnego folderu i przejdź do niego w terminalu. Skrypty uruchamiasz poleceniem php start.php; serwer WWW nie jest potrzebny, a wynik pojawia się w terminalu. Jeśli pobierasz pojedyncze pliki .php.txt ze strony zamiast ZIP, usuń końcówkę .txt lokalnie.
Cele i plan lekcji
Po zajęciach uczeń potrafi:
- wyjaśnić, na czym polega atak XSS i dlaczego dane od użytkownika są niezaufane;
- zamienić
<,>,&,"i'na encje funkcjąhtmlspecialchars; - wskazać, kiedy cudzysłowy i apostrofy muszą być zamieniane (atrybuty HTML);
- napisać funkcję pomocniczą
h()ze stałymi opcjami.
| Czas | Praca na lekcji |
|---|---|
| 0–5 min | Problem: komentarz z kodem <script> |
| 5–15 min | Encje HTML i htmlspecialchars |
| 15–22 min | Atrybuty, flagi, funkcja h() |
| 22–37 min | Trzy zadania samodzielne |
| 37–42 min | Kiedy zabezpieczenie jest potrzebne |
| 42–45 min | Pytania kontrolne |
Dlaczego trzeba zabezpieczać wyjście
Przeglądarka traktuje każdy fragment wyglądający jak znacznik HTML jako znacznik. Jeśli komentarz <script>alert(1)</script> trafi na stronę bez zmian, skrypt się wykona w przeglądarce każdego odwiedzającego. Funkcja htmlspecialchars zamienia znaki specjalne na encje: & na &, < na <, > na >, " na " i (z flagą ENT_QUOTES) ' na '. Przeglądarka wyświetli je jako zwykłe znaki, ale nie potraktuje jako kodu.
Zabezpieczenie stosujemy w miejscu wypisywania, a nie przy zapisie danych — ten sam tekst może trafić do HTML, do pliku tekstowego albo do JSON-a, a każdy kontekst wymaga innego zabezpieczenia. Szczególnie ważne są cudzysłowy: w atrybucie value="..." wpisany cudzysłów zamyka atrybut i pozwala dopisać własny, np. onfocus=.... Dlatego zawsze podajemy flagi ENT_QUOTES | ENT_SUBSTITUTE i kodowanie "UTF-8", najlepiej w jednej funkcji pomocniczej h(). Zabezpieczenie htmlspecialchars jest odwracalne (htmlspecialchars_decode), więc nie służy do ukrywania danych.
Zadania
Zadanie 1. Surowo i bezpiecznie. Napisz h(string $tekst): string (z flagami ENT_QUOTES | ENT_SUBSTITUTE i "UTF-8"). Wypisz tekst <script>alert("XSS")</script> na surowo i przez h().
Zadanie 2. Cudzysłowy. Dla tekstu O'Reilly & "Synowie" wypisz wynik h() oraz htmlspecialchars($cytat, ENT_NOQUOTES). Porównaj.
Zadanie 3. Atrybut. Zbuduj <input value="..."> z wartości " onfocus="alert(1) bez zabezpieczenia i z h(). Porównaj długość tekstu z zadania 1 przed i po zabezpieczeniu i sprawdź, czy da się go odwrócić.
Dla chętnych: porównaj htmlspecialchars z strip_tags dla komentarza Jan <jan@example.com> & Ewa.
Rozwiązanie i wyniki
<?php
function h(string $tekst): string
{
return htmlspecialchars($tekst, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
}
// Zadanie 1
$wpis = '<script>alert("XSS")</script>';
echo $wpis, "\n";
echo h($wpis), "\n";
// Zadanie 2
$cytat = "O'Reilly & \"Synowie\"";
echo h($cytat), "\n";
echo htmlspecialchars($cytat, ENT_NOQUOTES), "\n";
// Zadanie 3
$imie = '" onfocus="alert(1)';
echo '<input value="' . $imie . '">', "\n";
echo '<input value="' . h($imie) . '">', "\n";
echo strlen($wpis), " ", strlen(h($wpis)), "\n";
echo htmlspecialchars_decode(h($wpis)) === $wpis ? "odwracalne" : "nieodwracalne", "\n";
Program wypisze:
<script>alert("XSS")</script>
<script>alert("XSS")</script>
O'Reilly & "Synowie"
O'Reilly & "Synowie"
<input value="" onfocus="alert(1)">
<input value="" onfocus="alert(1)">
29 51
odwracalne
Zwróć uwagę na zadanie 3: bez zabezpieczenia wpisany tekst zamyka atrybut value i dopisuje nowy atrybut onfocus, a po zabezpieczeniu cały tekst zostaje wartością pola. Zadanie dla chętnych (rozszerzenie.php) wypisuje:
Jan <jan@example.com> & Ewa
Jan & Ewa
Typowe błędy
| Objaw | Co sprawdzić |
|---|---|
Znaki < znikają ze strony | Dane wypisano bez htmlspecialchars — przeglądarka uznała je za znacznik. |
| Apostrof nadal psuje atrybut | Brakuje flagi ENT_QUOTES (w starszych wersjach PHP domyślnie nie obejmuje apostrofu). |
Podwójne encje &amp; | Dane zabezpieczono dwa razy; zabezpieczaj raz, przy wypisywaniu. |
Polskie znaki zamieniają się w � lub puste | Brak "UTF-8" albo plik w innym kodowaniu. |
| Zabezpieczenie dodano przy zapisie do bazy | To zła warstwa; zapisuj dane surowe i zabezpieczaj przy wypisywaniu (a bazę chroń zapytaniami przygotowanymi). |
| Dane w JavaScript lub URL nadal niebezpieczne | htmlspecialchars dotyczy kontekstu HTML; URL i JS wymagają innych funkcji. |
Pytania kontrolne i punktacja
- Co zrobi
htmlspecialchars("<b>")? - Dlaczego zabezpieczenie stosujemy przy wypisywaniu, a nie przy zapisie?
- Po co flaga
ENT_QUOTES?
Odpowiedzi: Zwróci tekst <b>, który przeglądarka wyświetli jako <b>. Bo ten sam tekst może trafić do różnych kontekstów (HTML, JSON, plik), z których każdy wymaga innego zabezpieczenia. Zamienia także cudzysłowy i apostrofy, co chroni wartości atrybutów.
Proponowana punktacja (razem 10 punktów): zadanie 1 — funkcja h() z flagami 2 pkt, dwa wypisania 1 pkt; zadanie 2 — dwa wyniki 1 pkt, porównanie 1 pkt; zadanie 3 — atrybut bez i z zabezpieczeniem 2 pkt, długość i odwracalność 1 pkt, wyjaśnienie ataku 1 pkt. Zadanie dla chętnych nie jest wymagane do pełnej oceny.
Materiały do pobrania
Rozpakuj ZIP przed otwarciem plików. Materiały są dostępne bez logowania; publiczne rozwiązanie służy nauce i samodzielnemu sprawdzeniu, a nie poufnemu sprawdzianowi.
Co dalej?
Następny krok to walidacja danych z formularza, zapytania przygotowane do bazy i nagłówki bezpieczeństwa. Ten materiał rozwija dział o bezpieczeństwie aplikacji w kursie PHP w TablicaPro, gdzie znajdziesz prezentacje, ćwiczenia i testy do prowadzenia lekcji.
Źródła techniczne: htmlspecialchars w podręczniku PHP oraz XSS w OWASP. Zobacz też lekcję o formularzach PHP.
Poprowadź kolejne lekcje PHP
Pełny kurs: prezentacje, ćwiczenia i testy do pracy z klasą.
Kup dostęp do kursu PHP →