Совет по безопасности: Не используйте nl2br()!

Источник: «Security Tip: Don't Use nl2br()!»
Как бы полезно это ни звучало, но nl2br() может оставить вас уязвимым для межсайтового скриптинга (XSS)… Вместо него лучше использовать CSS!

PHP изобилует интересными и полезными функциями для различных задач. Одной из таких часто используемых функций является nl2br()! Если вы не знаете, nl2br() добавляет символы <br> в строки, где есть символы новой строки (\n, \n\r и \r).

Например:

> nl2br("One\nTwo\nThree");
= """
One<br />\n
Two<br />\n
Three
"
""

Наиболее распространённым вариантом использования, который я вижу для nl2br(), является отображение введённых пользователем данных из полей <textarea>. Она преобразует символы новой строки, введённые пользователем, в настоящие новые строки (через теги <br>), которые отображаются на странице.

Однако риск заключается в том, что при использовании nl2br() вам придётся выводить на страницу пользовательский ввод без экранирования, что может привести к появлению XSS уязвимостей.

Например, если мы используем такие данные введённые пользователем в следующих примерах:

One
Two
<img src=x onerror="alert('Boom!')">
Three

Вы не можете использовать nl2br() внутри тегов экранирования шаблонов blade:

{{ nl2br($input) }}

Так как он будет экранировать и теги <br>:

Всё содержимое в одной строке, без разрывов строк, с экранированием и отображением HTML.
Всё содержимое в одной строке, без разрывов строк, с экранированием и отображением HTML.

Но когда вы не экранируете вывод:

{!! nl2br($input) !!}

Вы получаете это:

Текст переносится на новые строки, но срабатывает полезная нагрузка XSS... 😔
Текст переносится на новые строки, но срабатывает полезная нагрузка XSS... 😔

Один из способов обойти эту проблему — экранировать внутри nl2br():

{!! nl2br(e($input)) !!}
Экранированный вывод (полезная нагрузка XSS видна) с переносами на новую строку.
Экранированный вывод (полезная нагрузка XSS видна) с переносами на новую строку.

Это работает, но выглядит довольно уродливо, и об этом легко забыть!

Гораздо лучший способ решить эту проблему — использовать CSS-правило white-space: pre-line; для экранированного ввода, не внося в него никаких изменений:

<div style="white-space: pre-line;">{{ $input }}</div>

Или если вы используете Tailwind CSS (Смотрите https://tailwindcss.com/docs/whitespace для всех вариантов форматирования вывода пробельных символов — там есть ещё несколько полезных способов!):

<div class="whitespace-pre-line">{{ $input }}</div>
Экранирование вывода с помощью CSS white-space: pre-line (полезная нагрузка XSS видна) с переносами на новую строку.
Экранирование вывода с помощью CSS white-space: pre-line (полезная нагрузка XSS видна) с переносами на новую строку.

Этот подход обеспечивает невероятно чистое решение (Не обращайте внимания на встроенные стили, они нужны только для демонстрации. В целом, встроенных стилей следует избегать, так как они могут привести к нарушению Content Security Policy!), использующее преимущества стандартного экранирования вывода для предотвращения проникновения XSS и сохраняющее перевод на новую строку в том виде, в котором его задумал пользователь. Кроме того, вы вряд ли забудете экранировать вывод, поскольку {{ ... }} должно использоваться по умолчанию.

Дополнительные материалы

Предыдущая Статья

Наука о JavaScript движке: Как компьютеры читают ваш код

Следующая Статья

Простое управление временными файлами в Laravel