Экранирование с e(), htmlspecialchars() и htmlentities()
e()
, htmlspecialchars()
и htmlentities()
? Можно ли просто использовать e()
для всего?На прошлой неделе мы рассмотрели, когда следует использовать strip_tags()
(спойлер: только при выводе вне каких-либо атрибутов или сложных структур), а теперь пришло время рассмотреть другие методы из этой коллекции: htmlspecialchars()
и htmlentities()
. Давайте также включим в список метод Laravel e()
, потому что он тесно связан с ним — я объясню, почему.
Чтобы объяснить, что делают эти функции, я закодирую одну и ту же строку в обеих, и вы сможете сравнить полученные результаты.
> $string = 'Hello <img src="x" onerror="alert(\'Boom!\')"> World!';
= "Hello <img src="x" onerror="alert('Boom!')"> World!"
> htmlspecialchars($string);
= "Hello <img src="x" onerror="alert('Boom!')"> World!"
> htmlentities($string);
= "Hello <img src="x" onerror="alert('Boom!')"> World!"
Замечаете различия? Нет, я тоже не заметил.
На самом деле, разница заметна только тогда, когда мы используем дополнительные символы:
> $string = " ¡¢£¥§©«®°¶·»¼½¾¿™";
= " ¡¢£¥§©«®°¶·»¼½¾¿™"
> htmlspecialchars($string);
= " ¡¢£¥§©«®°¶·»¼½¾¿™"
> htmlentities($string);
= " ¡¢£¥§©«®°¶·»¼½¾¿™"
htmlentities()
будет кодировать каждый специальный символ соответствующей HTML сущностью, в то время как htmlspecialchars()
будет кодировать только основное подмножество, которое обычно используется для атак межсайтового скриптинга (XSS) в HTML выводе: & " ' < >
Обратите внимание, что этот список не является исчерпывающим или идеальным — есть ситуации, в которых он не подходит. Я расскажу об этом в конце.
Если вы не знакомы с сущностями HTML, то они представляют собой специальный код, который веб-браузер понимает и автоматически преобразует в специальный символ при отображении.
Например, htmlspecialchars()
кодирует такие символы:
&
→&
'
→"
'
→'
<
→<
>
→>
Так как же e()
относится к этому?
Среди нескольких других помощников, метод Laravel e()
использует htmlspecialchars()
и является методом экранирования, повсеместно используемым Laravel. Laravel также устанавливает некоторые разумные значения по умолчанию и предоставляет обёртку с некоторыми дополнительными функциями, которые мы можем использовать. Это делает e()
пригодным для использования в большинстве мест (см. ниже), где требуется экранирование в HTML-выводе.
Я рекомендую использовать e()
всегда, когда вам нужно вручную экранировать вывод в HTML.
Тем не менее вы можете использовать любой из этих трёх методов, если это имеет смысл. Мне нравится e()
из-за его дополнительных функций и краткости, но htmlspecialchars()
тоже справляется с этой задачей, а если вам нужно закодировать дополнительные символы, то следует обратиться к htmlentities()
.
Почему же мы не видим e()
в шаблонах Blade? Технически это так! Экранирующие теги Blade, {{ ... }}
, используют e()
в фоновом режиме для экранирования вывода, делая его безопасным. Поэтому обращаться к e()
или другим тегам нужно только за пределами Blade или в сложных структурах, которые нельзя вывести с помощью {{ ... }}
.
Важное обновление (7 декабря)
В первоначальной версии этой статьи подразумевалось, что e()
и htmlspecialchars()
безопасно использовать для экранирования вывода в любом месте. Это попросту не так, и мне не следовало так формулировать статью. (Огромная благодарность Paul Moore за то, что он меня в этом разубедил!)
Экранирование вывода всегда требует понимания контекста, в который он выводится. Если вы выводите в HTML вне тегов, то < > &
— ваша самая большая проблема, внутри атрибутов вам придётся иметь дело с " '
. Что касается строк шаблонов Javascript — внезапно у нас появляются обратные кавычки ( `
) и знаки доллара ( $
), которые тоже нужно учитывать. Как насчёт пользовательского шаблонизатора, который, возможно, ищет {{ ... }}
, которые можно включить в пользовательский вывод, чтобы обойти Blade…
Я хочу сказать, что экранирование зависит от контекста. Если вы слепо экранируете вывод, используя обычную функцию, не учитывая контекст, в который вы выводите, есть шанс оставить вектор инъекции, ожидающий момента, когда кто-то найдёт его.
Прежде чем мы закончим, я хотел бы сделать небольшое замечание о PHP 8.0 и более ранних версиях!
В PHP 8.0 и более ранних версиях htmlspecialchars()
и htmlentities()
игнорировали одинарные кавычки и экранировали только двойные кавычки в соответствии с флагом по умолчанию ENT_COMPAT
. Это значение было изменено в PHP 8.1 на ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
, который по умолчанию кодирует оба типа кавычек.
Это открывает XSS-возможности при использовании одинарных кавычек в HTML, куда внедряются переменные, и поэтому, если в вашей среде используется PHP 8.0 или более ранняя версия, вы не сможете безопасно использовать htmlspecialchars()
или htmlentities()
, если не установите флаг ENT_QUOTES
вручную.
Обратите внимание, что e()
включает этот флаг с тех пор, как этот хелпер впервые был добавлен во фреймворк, 11 лет назад — так что я рекомендую использовать e()
.
Узнать больше
- PHP:
strip_tags
- PHP:
htmlspecialchars
- Manual - PHP:
htmlentities
- Manual e()
- Полную функцию можно найти в: src/Illuminate/Support/helpers.php. Мы уже рассказывали об интерфейсе
Htmlable
ранее, в статье Laravel Security In Depth: Escaping Output Safely и в статье Security Tip: Avoiding XSS with HtmlString.