htmlspecialchars($text) zamienia &, <, >, " i ' na &, <, >, " i '. Wywołuj ją na każdym fragmencie danych od użytkownika, który wypisujesz na stronę, a dane będą pokazywane jako tekst, zamiast być czytane jako HTML.
Blok wypisuje ten sam komentarz dwa razy, raz surowy i raz escapowany. Uruchom go i porównaj obie linie w zakładce Page, a potem wpisz w formularzu własny HTML, na przykład <h1>big</h1> albo <img src=x>, i naciśnij Show.
W surowej linii przeglądarka wykonuje znaczniki: pogrubione słowo jest pogrubione, a odwiedzający, który wpisze <script>, sprawi, że jego skrypt wykona się w przeglądarce każdego innego czytelnika. Ten atak nazywa się cross-site scripting (XSS). W linii escapowanej te same znaki przychodzą jako <b>, a przeglądarka rysuje je jako tekst. Przełącz się na zakładkę Output, aby zobaczyć encje, które faktycznie wypisało PHP.
Co zamienia htmlspecialchars
Pięć znaków i nic więcej. Litery, znaki diakrytyczne i emoji przechodzą bez zmian.
& jest na liście, bo od niego zaczyna się każda encja: gdyby go zostawić, komentarz wspominający < zostałby wyświetlony jako <.
Escapowanie atrybutów, nie tylko tekstu
Dane od użytkownika w atrybucie też trzeba escapować. Bez tego cudzysłów w wartości zamyka atrybut, a reszta danych staje się nowymi atrybutami. Tutaj „imię” przemyca atrybut style; uruchom i spójrz na oba pola.
W niebezpiecznym polu przeglądarka widzi value="Ada", a po nim nowy atrybut style, więc pole robi się czerwone i pokazuje tylko Ada. Atakujący napisałby tam onfocus="..." zamiast style, a jego kod by się wykonał. W bezpiecznym polu każdy " stał się ", więc cały string zostaje w value i jest pokazywany tak, jak go wpisano.
Od PHP 8.1 domyślne flagi to ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, więc pojedyncze cudzysłowy też są escapowane, a atrybuty zapisane w '...' są bezpieczne. Starszy kod często przekazuje ENT_QUOTES ręcznie, a w PHP 7 i wcześniejszych było to wymagane:
Krótka funkcja pomocnicza do szablonów
Pisanie dziesiątki razy w szablonie htmlspecialchars($x, ENT_QUOTES, 'UTF-8') wprowadza szum, więc większość projektów opakowuje to w jednoliterową funkcję. Silniki szablonów, takie jak Twig i Blade, robią to samo automatycznie dla każdego {{ $var }}.
Typ ?string i ?? '' mają znaczenie: przekazywanie null do htmlspecialchars() jest przestarzałe od PHP 8.1, a twoje PHP wypisałoby komunikat o przestarzałym użyciu dla każdego użytkownika bez biogramu.
Podwójne kodowanie i htmlspecialchars_decode
Jeśli wartość zostanie escapowana dwa razy, czytelnik widzi encje: & staje się & za pierwszym razem i &amp; za drugim, co przeglądarka pokazuje jako &. Zwykle oznacza to, że wartość została escapowana przy zapisie i ponownie przy wypisywaniu. Przekaż double_encode: false, aby zostawić istniejące encje, a htmlspecialchars_decode() użyj, aby wrócić.
Prawdziwe rozwiązanie to przechowywanie surowego tekstu i escapowanie tylko na wyjściu. double_encode: false jest na tekst, który już zawiera encje stworzone nie przez ciebie, na przykład importowany kanał.
htmlspecialchars vs htmlentities vs strip_tags
Te trzy często się myli. Blok uruchamia wszystkie na tych samych danych i pokazuje dla każdej, co wypisuje PHP i co robi z tym przeglądarka:
htmlspecialchars()escapuje pięć znaków HTML. Używaj jej do każdego tekstu wypisywanego do HTML.htmlentities()zamienia teżénaé. Przydawała się, gdy strony nie były w UTF-8; dziś tylko utrudnia czytanie źródła.strip_tags()usuwa znaczniki i zostawia ich tekst. Służy do zamiany HTML na zwykły tekst (podgląd e-maila, opis meta), a nie do bezpieczeństwa: ostatni wiersz pokazuje, że dozwolone<b>zachowuje swojeonclick, a tekst umieszczony w atrybucie nie jest w ogóle ruszany.
Gdzie htmlspecialchars nie wystarcza
htmlspecialchars() to właściwe escapowanie dla tekstu HTML i atrybutów w cudzysłowach. Inne miejsca na stronie mają inne zasady:
- W adresie URL
http_build_query()albourlencode()koduje wartość;htmlspecialchars()potem sprawia, że&między parametrami to poprawny HTML. - W JavaScripcie
json_encode()daje poprawną wartość JS, aJSON_HEX_TAGzamienia<i>na\u003Ci\u003E, więc</script>w danych nie może zamknąć znacznika skryptu. - Nigdy nie wypisuj danych od użytkownika w
hrefbez sprawdzenia schematu:htmlspecialchars('javascript:alert(1)')pozostaje bez zmian i nadal wykonuje się po kliknięciu. Akceptuj tylko adresyhttpihttps, jak pokazuje strona o filter_var.
Obsługę formularzy, która łączy to wszystko, opisuje strona o formularzach w PHP.
Najczęściej zadawane pytania
Co robi htmlspecialchars w PHP?
Zastępuje &, <, >, " i ' przez &, <, >, " i '. Przeglądarka wyświetla wtedy te znaki, zamiast czytać je jako HTML, więc <script> wpisane w formularz jest pokazywane jako tekst i nigdy się nie wykonuje.
Czym różni się htmlspecialchars od htmlentities?
htmlspecialchars() zamienia tylko pięć znaków specjalnych w HTML. htmlentities() zamienia także każdy znak, który ma nazwaną encję, więc café staje się café. Na stronach w UTF-8 obie są tak samo bezpieczne, a htmlspecialchars() zostawia wynik czytelnym, dlatego to ona jest zwykłym wyborem.
Czy w PHP 8 nadal potrzebuję ENT_QUOTES?
Nie ze względu na bezpieczeństwo: od PHP 8.1 domyślne flagi to ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, więc pojedyncze cudzysłowy też są escapowane. Wiele projektów nadal jawnie przekazuje ENT_QUOTES, 'UTF-8', żeby wywołanie działało tak samo w starszych wersjach i było oczywiste dla czytelników.
Używać htmlspecialchars na wejściu czy na wyjściu?
Na wyjściu. Przechowuj i waliduj surową wartość, a escapuj ją w chwili wypisywania do HTML. Escapowanie na wejściu zapisuje < w bazie danych, psuje długości i wyszukiwanie oraz prowadzi do podwójnego escapowania, takiego jak &lt;.
Czy strip_tags wystarczy do ochrony przed XSS?
Nie. strip_tags() usuwa znaczniki, ale parametr dozwolonych znaczników zachowuje ich atrybuty, więc <b onclick="..."> przetrwa, a tekst umieszczony w atrybucie nie jest w ogóle ruszany. Do wypisywania danych od użytkownika używaj htmlspecialchars().