АрхитектураАрхитектура безопасностиАрхитектор безопасности веб-приложений

Веб приложение иногда вставляет пользовательское содержимое в HTML без экранирования. Какой механизм на гра...

Веб-приложение иногда вставляет пользовательское содержимое в HTML без экранирования. Какой механизм на границе браузера ограничит выполнение внедрённого скрипта?

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

<h1>Профиль</h1>
<div>{{ user_bio }}</div>
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Нужно применять Content Security Policy (CSP) с политикой, запрещающей произвольное выполнение встроенных и загруженных скриптов, например через nonce или строгий список источников. CSP ограничивает последствия XSS, но не заменяет экранирование вывода: основная защита должна предотвращать попадание управляющей разметки в HTML.

Исторический контекст

CSP появился как дополнительный уровень защиты веб-приложений от внедрения скриптов. Исходная проблема заключалась в том, что одной ошибки в экранировании или небезопасной вставки данных достаточно для выполнения кода в браузере пользователя.

Политика переносит часть решения с разработчика каждого шаблона на границу между сервером и браузером. Браузер получает правила и самостоятельно блокирует нарушения, поэтому компрометация одного фрагмента HTML не обязательно приводит к немедленному выполнению произвольного JavaScript.

Постановка проблемы

В примере значение user_bio попадает в HTML. Если злоумышленник сохранит в нём тег <script> или обработчик события, браузер может выполнить его с полномочиями текущего сайта: прочитать доступные данные страницы, отправить запросы от имени пользователя или изменить интерфейс.

Неверно считать CSP полной заменой безопасной генерации HTML. Если политика разрешает unsafe-inline, использует слишком широкие источники или допускает небезопасные динамические загрузки, защитный эффект существенно снижается.

Подробное решение

Сервер должен отправлять CSP в HTTP-заголовке. Для скриптов предпочтительна политика с nonce: сервер создаёт случайное значение для конкретного ответа, указывает его в политике и добавляет тот же nonce только доверенным тегам скриптов.

Content-Security-Policy: default-src 'self'; script-src 'nonce-r4nd0m'; object-src 'none'; base-uri 'none'
<script nonce='r4nd0m'>initApp()</script>

Внедрённый тег без корректного nonce не выполнится. Значение nonce должно быть непредсказуемым, новым для каждого ответа и недоступным для подстановки злоумышленником; повторное использование фиксированного значения превращает механизм в формальность.

Другой вариант — хэширование конкретных неизменяемых встроенных скриптов. Оно подходит, когда содержимое скрипта стабильно, но неудобно для часто меняющихся шаблонов. Для современных приложений обычно избегают unsafe-inline и unsafe-eval, поскольку они расширяют разрешённую поверхность выполнения.

CSP ограничивает выполнение скриптов, источники загрузки, плагины, адреса отправки данных и другие типы ресурсов. Однако политика не исправляет серверную уязвимость, не защищает от кражи данных через разрешённые каналы автоматически и не устраняет XSS в контекстах, которые политика не покрывает.

Практически CSP сначала полезно включать в режиме Content-Security-Policy-Report-Only, чтобы обнаружить легитимные нарушения без блокировки. После анализа отчётов политику переводят в принудительный режим. Это создаёт компромисс между совместимостью и строгостью: слишком мягкая политика мало защищает, слишком строгая может сломать приложение.

Ситуация из практики

В личном кабинете обнаружили stored XSS в поле описания профиля. Рассматривались три варианта. Только экранирование HTML устраняло конкретную ошибку, но оставляло высокий риск при будущих ошибках в другом шаблоне. Разрешение всех скриптов с нужных доменов сохраняло совместимость, однако скомпрометированный скрипт на таком домене мог выполнять код в контексте приложения.

Выбрали исправление контекстного экранирования вместе со строгой CSP на nonce для доверенных скриптов, запретом объектов и ограничением источников. Сначала применили режим только отчётности, исправили несовместимые места, затем включили блокировку. В результате ошибка вывода перестала приводить к выполнению внедрённого скрипта, а последующие нарушения политики стали видны в централизованных отчётах.

Что кандидаты часто упускают

  1. Достаточно ли включить CSP с default-src 'self', чтобы закрыть XSS?

Нет. Такая политика может разрешить скрипты с собственного источника, а script-src может наследовать или переопределять поведение в зависимости от заданных директив. Нужно явно определить script-src, исключить опасные послабления и проверить, не допускает ли приложение небезопасные встроенные скрипты.

  1. Почему CSP нельзя считать заменой экранированию вывода?

CSP — это механизм снижения последствий, а не устранения причины. Политика может быть неправильно настроена, браузер может поддерживать её не полностью, а атака может использовать разрешённые скрипты, небезопасные DOM-контексты или другие каналы. Поэтому данные нужно экранировать в соответствии с контекстом: HTML, атрибут, URL или JavaScript.

  1. Чем nonce-политика отличается от разрешения домена приложения?

Разрешение домена доверяет любому подходящему скрипту, который браузер может загрузить с этого источника. Nonce разрешает только конкретные сервером отмеченные элементы текущего ответа, поэтому случайно внедрённый <script> без nonce блокируется. Однако если злоумышленник сможет украсть или предсказуемо получить nonce, либо внедрить код внутрь уже разрешённого скрипта, защита может быть обойдена; поэтому nonce должен генерироваться безопасно, а шаблоны — оставаться защищёнными.