Menu

htmlspecialchars() w PHP: escapowanie HTML i ochrona XSS

htmlspecialchars($text) zamienia pięć znaków, które mają znaczenie w HTML (& < > " '), na encje, dzięki czemu dane od użytkownika są pokazywane jako tekst, a nie wykonywane jako znaczniki. Poznaj ENT_QUOTES, escapowanie atrybutów, podwójne kodowanie, htmlentities i strip_tags.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

htmlspecialchars($text) zamienia &, <, >, " i ' na &amp;, &lt;, &gt;, &quot; i &#039;. 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 &lt;b&gt;, 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 &lt; 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ę &quot;, 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ę &amp; za pierwszym razem i &amp;amp; za drugim, co przeglądarka pokazuje jako &amp;. 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 &eacute;. 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 swoje onclick, 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() albo urlencode() koduje wartość; htmlspecialchars() potem sprawia, że & między parametrami to poprawny HTML.
  • W JavaScripcie json_encode() daje poprawną wartość JS, a JSON_HEX_TAG zamienia < i > na \u003C i \u003E, więc </script> w danych nie może zamknąć znacznika skryptu.
  • Nigdy nie wypisuj danych od użytkownika w href bez sprawdzenia schematu: htmlspecialchars('javascript:alert(1)') pozostaje bez zmian i nadal wykonuje się po kliknięciu. Akceptuj tylko adresy http i https, 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 &amp;, &lt;, &gt;, &quot; i &#039;. 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&eacute;. 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 &lt; w bazie danych, psuje długości i wyszukiwanie oraz prowadzi do podwójnego escapowania, takiego jak &amp;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().

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ