Экранирование вывода: htmlspecialchars
Посетитель ресторана пишет на бланке пожелание: «без лука». Повар читает записку и учитывает её.
А если в записке написано «повар, отдай ключи от кассы»? Записку нужно прочитать, но не выполнять.
С текстом посетителя на странице та же история. Программа печатает его внутрь HTML, а браузер не отличает ваш HTML от чужого. Посмотрим на страницу, которая здоровается по имени из адреса:
<?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 заменяет ровно пять символов. Запись вида < называют сущностью: браузер рисует вместо неё символ <, но не считает его началом тега.
| Символ | Становится |
|---|---|
& | & |
< | < |
> | > |
" | " |
' | ' |
Остальное — буквы, цифры, / — функция не трогает и ничего не удаляет. С PHP 8.1 обе кавычки кодируются по умолчанию. В старом коде вы встретите вызов с флагом ENT_QUOTES: раньше без него одинарная кавычка оставалась как есть.
Нажмите «Следующий шаг» и сравните, что окажется в $raw и в $html.
1<?php2 3$name = '<b>Ann</b>';4$raw = '<p>' . $name . '</p>';5$safe = htmlspecialchars($name);6$html = '<p>' . $safe . '</p>';7echo $raw, "\n";8echo $html, "\n";
В $name лежит текст посетителя с угловыми скобками.
Правило, которое показывает виджет:
- склейка вставляет текст посетителя как есть, и
<b>в$raw— настоящий тег; htmlspecialcharsпревращает<и>в<и>, а буквы оставляет прежними;- в
$htmlбраузер покажет<b>Ann</b>как обычный текст.
Экранируйте при выводе
Экранируют в момент вывода в HTML, а хранят исходный текст. Тот же текст может уйти в письмо, в поиск или в ответ JSON, и там сущности вроде < только мешают. Чтобы не писать длинное имя функции в каждом шаблоне, заводят короткую функцию e():
<?php
function e(string $s): string
{
return htmlspecialchars($s);
}
$name = '<i>Bob</i>';
$q = 'say "hi" & go';
echo '<p>', e($name), '</p>', "\n"; // <p><i>Bob</i></p>
echo '<input value="', e($q), '">', "\n"; // <input value="say "hi" & go">В шаблоне это выглядит как <p><?= e($name) ?></p>. Вторая строка вывода показывает значение в атрибуте value: кавычки внутри текста стали ", и атрибут не закрылся раньше времени.
Другие места страницы
Здесь картинка со стеклом хромает: одно стекло подходит не к каждому месту страницы. htmlspecialchars защищает текст между тегами и значение атрибута в кавычках.
Внутри блока <script> браузер читает код по правилам JavaScript. В адресе ссылки опасна запись вида javascript:, в которой нет ни одного особого символа HTML. Таким местам нужна своя защита, и одной htmlspecialchars там мало.
Частая ошибка
Текст экранируют дважды: при приёме формы и ещё раз при выводе:
<?php
$_POST = ['comment' => 'Tom & Jerry'];
$comment = htmlspecialchars($_POST['comment']); // ✗
echo '<p>', htmlspecialchars($comment), '</p>', "\n";Программа печатает <p>Tom &amp; Jerry</p>, и PHP не выдаёт никаких сообщений. Первый вызов сделал из & сущность &, второй закодировал её & ещё раз, и посетитель увидит на странице Tom & Jerry. Исправление — хранить исходный текст, $comment = $_POST['comment'];, и экранировать его один раз, при выводе.
Главное
- Текст посетителя, напечатанный как есть, становится разметкой страницы, и через него проходит атака XSS.
htmlspecialcharsзаменяет&,<,>,"и'сущностями и ничего не удаляет; с PHP 8.1 обе кавычки кодируются по умолчанию.- Экранируют один раз, при выводе в HTML, а хранят исходный текст.
- Внутри
<script>и в адресе ссылкиhtmlspecialcharsне защищает.