
XSS (Cross-Site Scripting, межсайтовый скриптинг) — внедрение чужого JavaScript, который выполняется в браузере жертвы в контексте вашего сайта. Через XSS крадут сессии и токены, подделывают интерфейс, проводят фишинг прямо на легитимном домене и совершают действия от имени пользователя. Несмотря на возраст, XSS остаётся в OWASP Top 10 2021 (категория A03: Injection) и входит в топ выплат почти на всех bug bounty программах. Это одна из самых частых находок на пентесте веб-приложений.
Три типа XSS — с примерами полезной нагрузки
1. Reflected (отражённый)
Скрипт передаётся в запросе и сразу «отражается» в ответе. Классический пример — поиск, который выводит запрос без экранирования:
https://site.com/search?q=Жертву заманивают по ссылке (через письмо, мессенджер, рекламу). Опасность ограничена тем, что нужна активная доставка ссылки.
2. Stored (хранимый) — самый опасный
Скрипт сохраняется на сервере — в комментарии, имени профиля, отзыве, поле адреса — и выполняется у каждого, кто открывает страницу. Ссылка не нужна: достаточно, чтобы жертва зашла на заражённую страницу. Один такой XSS в админ-панели = компрометация всех администраторов.
3. DOM-based
Уязвимость целиком на стороне клиента: небезопасная работа JavaScript с данными. Источник (source) — например location.hash, приёмник (sink) — innerHTML:
// ❌ УЯЗВИМО: данные из URL напрямую в HTML
document.getElementById("out").innerHTML = location.hash.slice(1);
// атака: site.com/#
DOM-XSS особенно актуален для SPA на React, Vue, Angular, где логика вынесена в браузер.
Что реально делают через XSS
- Кража сессии. Отправка
document.cookieна сервер атакующего (если нет флага HttpOnly). - Кража токенов из localStorage. SPA часто хранят JWT в
localStorage— он полностью доступен из JS. - Действия от имени жертвы. Скрипт делает запросы с её правами (перевод, смена email, создание админа).
- Кейлоггер и фейковые формы. Перехват вводимых логина/пароля прямо на настоящем домене.
- BeEF-хук. Полный контроль над браузером жертвы через фреймворк эксплуатации.
Защита: пять слоёв, а не один фильтр
XSS не закрывается «чёрным списком запрещённых слов» — это тупиковый путь, обходится десятками способов. Работает многослойная защита:
- Контекстное экранирование вывода. Кодируйте данные по месту вставки — HTML, атрибут, JS, URL требуют разного экранирования. Используйте API фреймворка, а не ручную склейку строк.
- Content Security Policy (CSP). Заголовок, который запрещает выполнение постороннего скрипта даже при наличии инъекции.
- Безопасные API вместо innerHTML. В React/Vue избегайте
dangerouslySetInnerHTML/v-html; для очистки HTML — библиотека DOMPurify. - HttpOnly и Secure на cookie сессии. Тогда даже сработавший XSS не украдёт сессионную куку.
- Валидация ввода как дополнительный барьер (например, email действительно похож на email).
Пример строгой CSP, которая нейтрализует большинство XSS:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'И безопасная очистка пользовательского HTML (например, для комментариев с форматированием):
import DOMPurify from "dompurify";
el.innerHTML = DOMPurify.sanitize(userHtml, { ALLOWED_TAGS: ["b","i","a","p","ul","li"] });Экспертное наблюдение: самые дорогие XSS мы находим не в блоге, а там, где данные проходят «служебным» путём — в письмах-уведомлениях, PDF-генераторах, экспортах и админ-логах. Разработчик экранировал вывод в шаблоне сайта, но забыл про рассылку, которую читает бухгалтер или админ.
Как это тестируется
Найти XSS во всех контекстах вывода можно только ручной проверкой каждой точки ввода: формы, параметры URL, заголовки, загружаемые файлы, imported-данные. Для SPA отдельно анализируется клиентская логика (source → sink). Мы делаем это на пентесте веб-приложений и пентесте SPA.
На вашем сайте точно нет XSS?
Проверим все точки ввода и контексты вывода — reflected, stored и DOM-based — и покажем, как их закрыть.
Читайте также: IDOR на практике, Пентест SPA.