XSS: почему сегодня DOM XSS часто интереснее классики
Сегодня хочу осветить тему XSS и особенно уделить внимание DOM XSS.
Так как по мне классические векторы на reflected и stored XSS в приватных программах и зрелых продуктах часто уже неплохо прикрыты: их ловят на ранних этапах, на ревью, в сканерах и при первых же волнах тестирования.
А вот DOM XSS до сих пор регулярно остаётся в тени, хотя по импакту может быть не менее неприятным. В этом посте кратко разберу основные виды XSS, отдельно подсвечу DOM XSS и в конце дам мини-итог по защите.
━━━━━━━━━━ Что такое XSS ━━━━━━━━━━
XSS (Cross-Site Scripting) — это уязвимость, при которой приложение позволяет внедрить пользовательские данные в HTML или JavaScript так, что браузер выполняет их как код. Важно понимать 2 вещи: • XSS срабатывает на стороне клиента, то есть в браузере • причина почти всегда появляется на этапе разработки: данные не закодировали, не экранировали или вставили в опасный sink
━━━━━━━━━━ Основные виды XSS ━━━━━━━━━━
➤ Reflected XSS Данные приходят из запроса и сразу отражаются в ответе страницы.
Пример логики: • GET /search?q=<script>alert(1)</script>
Если параметр q без экранирования попадает в HTML, получаем XSS. Чаще всего такая атака требует перехода жертвы по подготовленной ссылке.
➤ Stored XSS Данные сначала сохраняются на сервере, а потом отображаются другим пользователям.
Типовые места: • комментарии • профиль пользователя • название проекта • ...
Stored XSS обычно ценнее reflected, потому что payload живёт в системе и срабатывает повторно.
➤ DOM XSS Уязвимость возникает уже в клиентском JavaScript. Сервер может вернуть безопасный HTML, но frontend сам создаёт проблему, когда берёт данные из небезопасного источника и вставляет их в опасное место DOM.
Классический пример: • данные из location.hash попали в innerHTML
━━━━━━━━━━ Видимые и слепые XSS ━━━━━━━━━━
• Visible XSS — результат видно сразу: alert, изменение DOM, подмена текста, выполнение JS в текущей странице
• Blind XSS — payload срабатывает позже, часто у другого пользователя, например у администратора
━━━━━━━━━━ Как работает DOM XSS ━━━━━━━━━━
DOM XSS удобно смотреть через цепочку: Source → processing → Sink
Source — откуда JS берёт данные: • location.search • location.hash • location.href • document.referrer • localStorage/sessionStorage • cookies, если их читает JS
Sink — куда данные попадают и могут выполниться как код: • innerHTML • outerHTML • document.write() • insertAdjacentHTML() • eval() • setTimeout("...")
DOM XSS появляется, когда данные из source доходят до sink без безопасной обработки.
Именно поэтому искать DOM XSS удобно через taint flow в браузере: откуда пришли данные, как преобразовались и в какой sink попали.
━━━━━━━━━━ Как DOM XSS появляется при разработке ━━━━━━━━━━
Чаще всего из-за вполне “обычных” задач: • разработчик хочет показать на странице поисковый запрос из URL • SPA использует location.hash для роутинга (маршрутизация) • данные из localStorage считают “безопасными”, потому что они локальные • postMessage принимают без нормальной валидации origin и содержимого
То есть проблема не в дырявом браузере, а в том, что пользовательский ввод попал в опасный контекст.
━━━━━━━━━━ Как защищаться от XSS ━━━━━━━━━━
Защита строится не по типу XSS, а по контексту вставки данных.
Что реально важно: • считать любой пользовательский ввод опасным • не доверять данным только потому, что они пришли из БД, localStorage или внутреннего API • экранировать данные по контексту: HTML, attribute, JS, URL • не вставлять пользовательские данные в innerHTML, outerHTML, document.write() • по возможности использовать textContent, innerText, безопасные DOM API • не использовать eval, new Function, строковые setTimeout/setInterval • валидировать и жёстко ограничивать данные из postMessage • включать CSP как дополнительный защитный слой • в шаблонизаторах и frontend-фреймворках не отключать escaping без реальной необходимости • при ревью искать именно связки source → sink, а не только payload в ответе
#AppSec #XSS #DOMXSS #StoredXSS #ReflectedXSS #WebSecurity #BugBounty #CyberSecurity