Skip to content

XSS

JuniorMiddleSeniorФундамент ~40 мин

Границы темы

Scope: выполнение attacker-controlled JavaScript или активного HTML в origin приложения из-за небезопасного потока данных.

Не рассматриваем: каталог payload, browser exploits и WAF rules. Защита строится на корректном использовании parser context и безопасных API, а не на denylist строк.

Результаты обучения

После статьи читатель сможет:

  1. построить цепочку source → transform → sink;
  2. определить parser context в точке вставки;
  3. выбрать output encoding, sanitization или safe sink;
  4. объяснить, почему CSP и HttpOnly уменьшают риск, но не устраняют XSS;
  5. проверить исправление безопасным учебным тестом.

Основная модель

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

Место, где данные интерпретируются: DOM API, SQL executor, shell, template engine или другой потребитель., где браузер трактует их как HTML, JavaScript, URL или

CSS с исполняемой семантикой. Основной вопрос не «есть ли в строке <script>», а «какой parser прочитает данные и в каком context».

Диаграмма

Основные термины

ТерминСмыслПример
SourceОткуда приходят недоверенные данныеURL, postMessage, API, DB
TransformКак данные меняютсяdecode, concatenate, template render
SinkAPI/позиция, интерпретирующая данныеinnerHTML, attribute, script
ContextParser и грамматика в месте вставкиHTML, attribute, URL, JS, CSS
EncodingПредставление символов как данных конкретного contextHTML entities
SanitizationУдаление/нормализация опасной разметки при разрешённом HTMLHTML sanitizer

End-to-end сценарий

Stored XSS

Диаграмма

Reflected и DOM-based

ТипГде возникает опасная вставкаДанные сохраняются
StoredServer output или client render данных из storageОбычно да
ReflectedServer отражает request data в responseНет
DOM-basedClient JavaScript переносит source в sinkНе обязательно

Классификация помогает искать поток, но защита всё равно определяется конечным context и sink.

Что происходит под капотом

HTML-документ обрабатывают несколько грамматик. Строка, безопасная как HTML text, не обязательно безопасна внутри JavaScript string или URL.

text
HTML document
├── text node              → HTML text context
├── attribute="..."        → HTML attribute context
├── href="..."             → URL + attribute context
├── <script>...</script>   → JavaScript context
└── style="..."            → CSS + attribute context
ContextПредпочтительный подходОпасный пример
HTML textFramework auto-escape или textContentinnerHTML
Quoted attributeAuto-escape + allowlist имени attributeEvent handler attribute
URLРазбор URL, allowlist scheme, затем attribute encodingjavascript:
JavaScriptНе вставлять данные в executable codeString concatenation в script
CSSНе вставлять недоверенные данные; строгая allowlistDynamic style expression
Rich HTMLПроверенный HTML sanitizerСамодельный regex

Encoding применяется на output под конкретный context. Раннее «экранирование при вводе» портит данные и не знает будущего context; validation решает допустимость данных, но не заменяет output encoding.

Минимальный воспроизводимый пример

Уязвимый вариант

html
<label for="search">Поиск</label>
<input id="search" />
<div id="result"></div>

<script>
  const query = new URLSearchParams(location.search).get("q") ?? "";
  document.querySelector("#result").innerHTML = `Результат: ${query}`;
</script>

Недоверенный q попадает в HTML sink. Не проверяйте пример на чужом сайте и не используйте payload, выполняющий внешние действия.

Исправленный вариант для обычного текста

html
<script>
  const query = new URLSearchParams(location.search).get("q") ?? "";
  const result = document.querySelector("#result");
  result.textContent = `Результат: ${query}`;
</script>

textContent создаёт text node: браузер не передаёт строку HTML parser.

Если действительно нужен rich HTML

ts
// Конкретную библиотеку и её конфигурацию нужно ревьюить и регулярно обновлять.
const cleanHtml = sanitizer.sanitize(untrustedHtml);
container.innerHTML = cleanHtml;
Разбор и очистка разрешённой разметки с удалением опасных элементов, атрибутов и URL. нужна только когда бизнес-требование

разрешает ограниченный HTML. Для обычного текста

API вывода, который трактует недоверенное значение как данные, а не как исполняемый код или разметку. проще и надёжнее.

Как проверить защиту

  1. Проследить все sources до sinks статическим анализом и code review.
  2. Проверить, что текст с <, >, ", ' отображается как текст.
  3. Проверить URL schemes и event handler attributes.
  4. Добавить regression test на конкретный найденный flow.
  5. Включить CSP report-only, собрать нарушения и затем ужесточить policy.

Проверка одной строкой не доказывает отсутствие XSS: важны все context и повторные decode/transform этапы.

Ограничения и trade-offs

  • Auto-escaping framework защищает стандартные template bindings, но escape hatches (v-html, dangerouslySetInnerHTML) возвращают ответственность разработчику.
  • Sanitization сохраняет часть HTML, но требует зрелого parser-based sanitizer и обновлений.
  • CSP ограничивает источники и выполнение script, но сложна в настройке и является defense in depth.
  • Trusted Types может перекрыть классы DOM sinks, но требует миграции и поддержки конкретных браузеров.
  • HttpOnly не даёт JavaScript прочитать cookie, но XSS всё ещё может выполнять authenticated requests из origin пользователя.

Failure modes и типичные ошибки

ОшибкаПочему не работаетИсправление
Удалять слово scriptМного parser contexts и обходовSafe sinks/context encoding
Один encoder для всегоContext имеют разные грамматикиEncoding в точке output
Sanitization через regexHTML не является регулярным языком для такого разбораParser-based sanitizer
Полагаться только на CSPPolicy может быть слабой или обходиться существующим кодомИсправить source-to-sink flow
Полагаться на WAFDOM flow может не проходить через serverИсправить application code
Считать HttpOnly защитой от XSSУменьшает часть impact, не останавливает executionУстранить unsafe sink

Безопасность и производительность

Централизованные safe UI primitives уменьшают число решений на каждом call site. Sanitization дорогая относительно textContent, поэтому не следует разрешать rich HTML без требования. При кешировании sanitized output важно инвалидировать его после обновления sanitizer или policy.

Практическое задание

Создайте локальную страницу с тремя sources: query string, API response и postMessage. Добавьте безопасный text output и отдельный rich-text output через sanitizer.

Критерии готовности:

  • для каждого source задокументирован конечный sink;
  • обычный текст нигде не проходит через HTML parser;
  • rich HTML очищается в одном централизованном месте;
  • URL принимает только https: и ожидаемые relative paths;
  • есть regression tests и CSP report-only.
Подсказка

Составьте таблицу source → transforms → sink → context → control. Начинайте с замены unsafe sink, а не с фильтрации входной строки.

Проверка знаний

ИНТЕРАКТИВНАЯ ПРОВЕРКА

XSS: data flow и контексты

0 / 3
01Восстановите модель прохождения недоверенных данных.
  1. Sink интерпретирует данные
  2. Данные поступают из source
  3. Возникает выполнение или другой impact
  4. Данные проходят transforms
02Как безопаснее вывести обычный недоверенный текст в DOM?
03Какие меры относятся к defense in depth, но не заменяют безопасный sink?
Проверено: 0 из 3

Самопроверка

  1. Почему HTML encoding нельзя бездумно применять внутри JavaScript context?
  2. Чем validation отличается от output encoding?
  3. Когда нужен sanitizer вместо textContent?
  4. Почему CSP не является основной защитой?
  5. Как HttpOnly меняет impact успешной XSS?
Ответы и критерии
  1. JavaScript parser использует другую грамматику; HTML-safe строка может оставаться опасной для JS.
  2. Validation решает, допустимы ли данные; encoding безопасно представляет допустимые данные в конкретном output context.
  3. Только когда продукт действительно разрешает ограниченный rich HTML.
  4. Она не исправляет небезопасный data flow, зависит от корректности policy и служит дополнительным барьером.
  5. Запрещает прямое чтение cookie через JS, но не мешает выполнять действия от имени пользователя или читать доступные DOM/API-данные.

Краткое резюме

Устойчивая модель XSS — это не blacklist payload, а цепочка source → transforms → sink → parser context. Предпочитайте safe sinks, применяйте context-aware encoding, используйте sanitizer только для разрешённого HTML, а CSP, Trusted Types и cookie attributes — как дополнительные слои.

Следующие шаги

  • Same-Origin Policy и browser security model;
  • CSP и Trusted Types;
  • cookies, sessions и CSRF;
  • template injection;
  • secure code review и static analysis.

Источники