- Sink интерпретирует данные
- Данные поступают из source
- Возникает выполнение или другой impact
- Данные проходят transforms
Тема
Тема
Scope: выполнение attacker-controlled JavaScript или активного HTML в origin приложения из-за небезопасного потока данных.
Не рассматриваем: каталог payload, browser exploits и WAF rules. Защита строится на корректном использовании parser context и безопасных API, а не на denylist строк.
После статьи читатель сможет:
HttpOnly уменьшают риск, но не устраняют XSS;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 |
| Sink | API/позиция, интерпретирующая данные | innerHTML, attribute, script |
| Context | Parser и грамматика в месте вставки | HTML, attribute, URL, JS, CSS |
| Encoding | Представление символов как данных конкретного context | HTML entities |
| Sanitization | Удаление/нормализация опасной разметки при разрешённом HTML | HTML sanitizer |
| Тип | Где возникает опасная вставка | Данные сохраняются |
|---|---|---|
| Stored | Server output или client render данных из storage | Обычно да |
| Reflected | Server отражает request data в response | Нет |
| DOM-based | Client JavaScript переносит source в sink | Не обязательно |
Классификация помогает искать поток, но защита всё равно определяется конечным context и sink.
HTML-документ обрабатывают несколько грамматик. Строка, безопасная как HTML text, не обязательно безопасна внутри JavaScript string или URL.
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 text | Framework auto-escape или textContent | innerHTML |
| Quoted attribute | Auto-escape + allowlist имени attribute | Event handler attribute |
| URL | Разбор URL, allowlist scheme, затем attribute encoding | javascript: |
| JavaScript | Не вставлять данные в executable code | String concatenation в script |
| CSS | Не вставлять недоверенные данные; строгая allowlist | Dynamic style expression |
| Rich HTML | Проверенный HTML sanitizer | Самодельный regex |
Encoding применяется на output под конкретный context. Раннее «экранирование при вводе» портит данные и не знает будущего context; validation решает допустимость данных, но не заменяет output encoding.
<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, выполняющий внешние действия.
<script>
const query = new URLSearchParams(location.search).get("q") ?? "";
const result = document.querySelector("#result");
result.textContent = `Результат: ${query}`;
</script>textContent создаёт text node: браузер не передаёт строку HTML parser.
// Конкретную библиотеку и её конфигурацию нужно ревьюить и регулярно обновлять.
const cleanHtml = sanitizer.sanitize(untrustedHtml);
container.innerHTML = cleanHtml;разрешает ограниченный HTML. Для обычного текста
API вывода, который трактует недоверенное значение как данные, а не как исполняемый код или разметку. проще и надёжнее.<, >, ", ' отображается как текст.Проверка одной строкой не доказывает отсутствие XSS: важны все context и повторные decode/transform этапы.
v-html, dangerouslySetInnerHTML) возвращают ответственность разработчику.HttpOnly не даёт JavaScript прочитать cookie, но XSS всё ещё может выполнять authenticated requests из origin пользователя.| Ошибка | Почему не работает | Исправление |
|---|---|---|
Удалять слово script | Много parser contexts и обходов | Safe sinks/context encoding |
| Один encoder для всего | Context имеют разные грамматики | Encoding в точке output |
| Sanitization через regex | HTML не является регулярным языком для такого разбора | Parser-based sanitizer |
| Полагаться только на CSP | Policy может быть слабой или обходиться существующим кодом | Исправить source-to-sink flow |
| Полагаться на WAF | DOM 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.
Критерии готовности:
https: и ожидаемые relative paths;Составьте таблицу source → transforms → sink → context → control. Начинайте с замены unsafe sink, а не с фильтрации входной строки.
textContent?HttpOnly меняет impact успешной XSS?Устойчивая модель XSS — это не blacklist payload, а цепочка source → transforms → sink → parser context. Предпочитайте safe sinks, применяйте context-aware encoding, используйте sanitizer только для разрешённого HTML, а CSP, Trusted Types и cookie attributes — как дополнительные слои.