Перейти к содержимому
Теория · PHP и веб · урок 4 из 7

Экранирование вывода: htmlspecialchars

Посетитель ресторана пишет на бланке пожелание: «без лука». Повар читает записку и учитывает её.

А если в записке написано «повар, отдай ключи от кассы»? Записку нужно прочитать, но не выполнять.

С текстом посетителя на странице та же история. Программа печатает его внутрь HTML, а браузер не отличает ваш HTML от чужого. Посмотрим на страницу, которая здоровается по имени из адреса:

PHP
<?php

$_GET = ['name' => '<b>Ann</b>'];
$name = $_GET['name'] ?? 'guest';
echo '<p>Hello, ', $name, '</p>', "\n"; // <p>Hello, <b>Ann</b></p>

Посетитель открыл адрес ?name=<b>Ann</b>, и браузер показал имя жирным. Текст посетителя стал частью разметки страницы. Вместо <b> можно прислать <script> с программой на JavaScript, и браузер выполнит её у каждого, кто откроет такую ссылку.

Атака XSS

XSS — атака, при которой чужой текст попадает на страницу как разметка или скрипт и срабатывает в браузере другого человека. Такой скрипт работает с правами вашего сайта: читает страницу и отправляет запросы от имени посетителя. Что такое XSS и как от неё защищаются, часто спрашивают на собеседовании.

Защита — экранирование: перед выводом особые символы HTML заменяют записями, которые браузер показывает как текст. В картинке с рестораном это записка посетителя за стеклом: её видно и можно прочитать, но выполнить нельзя.

htmlspecialchars

Функция htmlspecialchars заменяет ровно пять символов. Запись вида &lt; называют сущностью: браузер рисует вместо неё символ <, но не считает его началом тега.

СимволСтановится
&&amp;
<&lt;
>&gt;
"&quot;
'&#039;

Остальное — буквы, цифры, / — функция не трогает и ничего не удаляет. С PHP 8.1 обе кавычки кодируются по умолчанию. В старом коде вы встретите вызов с флагом ENT_QUOTES: раньше без него одинарная кавычка оставалась как есть.

Нажмите «Следующий шаг» и сравните, что окажется в $raw и в $html.

ВживуюТекст посетителя до и после htmlspecialcharsшаг 1 из 7 · рамок: 1

В $name лежит текст посетителя с угловыми скобками.

Сравните $raw и $html

Правило, которое показывает виджет:

  • склейка вставляет текст посетителя как есть, и <b> в $raw — настоящий тег;
  • htmlspecialchars превращает < и > в &lt; и &gt;, а буквы оставляет прежними;
  • в $html браузер покажет <b>Ann</b> как обычный текст.

Экранируйте при выводе

Экранируют в момент вывода в HTML, а хранят исходный текст. Тот же текст может уйти в письмо, в поиск или в ответ JSON, и там сущности вроде &lt; только мешают. Чтобы не писать длинное имя функции в каждом шаблоне, заводят короткую функцию e():

PHP
<?php

function e(string $s): string
{
    return htmlspecialchars($s);
}

$name = '<i>Bob</i>';
$q = 'say "hi" & go';
echo '<p>', e($name), '</p>', "\n"; // <p>&lt;i&gt;Bob&lt;/i&gt;</p>
echo '<input value="', e($q), '">', "\n"; // <input value="say &quot;hi&quot; &amp; go">

В шаблоне это выглядит как <p><?= e($name) ?></p>. Вторая строка вывода показывает значение в атрибуте value: кавычки внутри текста стали &quot;, и атрибут не закрылся раньше времени.

Другие места страницы

Здесь картинка со стеклом хромает: одно стекло подходит не к каждому месту страницы. htmlspecialchars защищает текст между тегами и значение атрибута в кавычках.

Внутри блока <script> браузер читает код по правилам JavaScript. В адресе ссылки опасна запись вида javascript:, в которой нет ни одного особого символа HTML. Таким местам нужна своя защита, и одной htmlspecialchars там мало.

Частая ошибка

Текст экранируют дважды: при приёме формы и ещё раз при выводе:

PHP
<?php

$_POST = ['comment' => 'Tom & Jerry'];
$comment = htmlspecialchars($_POST['comment']); // ✗
echo '<p>', htmlspecialchars($comment), '</p>', "\n";

Программа печатает <p>Tom &amp;amp; Jerry</p>, и PHP не выдаёт никаких сообщений. Первый вызов сделал из & сущность &amp;, второй закодировал её & ещё раз, и посетитель увидит на странице Tom &amp; Jerry. Исправление — хранить исходный текст, $comment = $_POST['comment'];, и экранировать его один раз, при выводе.

Главное

  • Текст посетителя, напечатанный как есть, становится разметкой страницы, и через него проходит атака XSS.
  • htmlspecialchars заменяет &, <, >, " и ' сущностями и ничего не удаляет; с PHP 8.1 обе кавычки кодируются по умолчанию.
  • Экранируют один раз, при выводе в HTML, а хранят исходный текст.
  • Внутри <script> и в адресе ссылки htmlspecialchars не защищает.