
XSS (Cross-Site Scripting) — внедрение чужого JavaScript, который выполняется в браузере жертвы в контексте вашего сайта. Через XSS крадут сессии, угоняют аккаунты, ставят скиммеры карт и фишат прямо на легитимном домене. Уязвимости почти 20 лет, но она в OWASP Top 10 2021 (A03) и стабильно в топе выплат bug bounty. И это одна из самых частых наших находок на пентесте веб-приложений.
XSS, которые вошли в историю
- Червь Samy (MySpace, 2005) — хранимый XSS в профиле добавлял автора в друзья и копировал себя в профиль жертвы. За ~20 часов — больше миллиона «друзей». Классика, показавшая мощь stored XSS.
- British Airways (2018) — атака Magecart: внедрённый в сайт скрипт-скиммер увёл данные ~380 000 карт. Итог — штраф ICO £20 млн по GDPR. XSS/инъекция скрипта = прямые деньги.
- TweetDeck (2014) — самораспространяющийся XSS-червь заставил тысячи аккаунтов автоматически ретвитить эксплойт; Twitter пришлось временно отключить сервис.
Три типа XSS — с рабочими пейлоадами
1. Reflected (отражённый)
Скрипт передаётся в запросе и сразу отражается в ответе. Классика — поиск без экранирования:
https://site.com/search?q=">2. Stored (хранимый) — самый опасный
Скрипт сохраняется на сервере (комментарий, имя, отзыв, адрес) и срабатывает у каждого, кто открывает страницу. Ссылка не нужна. Один stored XSS в админ-панели = все админы скомпрометированы. Именно этот тип даёт червей вроде Samy.
3. DOM-based
Уязвимость целиком на клиенте: небезопасная работа JS с данными. Источник (source) → приёмник (sink):
// ❌ данные из URL напрямую в HTML
document.getElementById("out").innerHTML = location.hash.slice(1);
// payload: site.com/#
Актуально для SPA на React/Vue/Angular. И запомните: alert(1) в пейлоадах — это просто маркер «код выполнился». Реальная нагрузка ворует куки и токены.
Что реально делают через XSS
- Кража сессии: отправка
document.cookie(если нет HttpOnly). - Кража токенов из localStorage: SPA часто хранят JWT в
localStorage— он полностью читается из JS. Аргумент против хранения токенов там. - Действия от имени жертвы: скрипт делает запросы с её правами — смена email, создание админа, перевод.
- Скиммер карт (как British Airways) и фейковые формы логина прямо на настоящем домене.
- BeEF-хук — полный контроль над браузером жертвы.
Защита: пять слоёв, а не чёрный список
Фильтровать «запрещённые слова» бесполезно — обходится атрибутами, событиями, SVG, кодированием. Работает многослойная защита:
- Контекстное экранирование вывода. HTML, атрибут, JS и URL требуют разного экранирования. Используйте API фреймворка, не ручную склейку строк.
- Content Security Policy (CSP). Нейтрализует большинство XSS даже при наличии инъекции.
- DOMPurify для пользовательского HTML; в React/Vue избегайте
dangerouslySetInnerHTML/v-html. - HttpOnly + Secure + SameSite на cookie сессии — тогда сработавший XSS не украдёт сессию.
- Валидация ввода как дополнительный барьер.
# строгая 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, заголовки (Referer, User-Agent), загружаемые файлы (имена, метаданные), импортируемые данные. Для SPA отдельно трассируется цепочка source→sink. Делаем это на пентесте веб-приложений и пентесте SPA.
На вашем сайте точно нет XSS?
Проверим все точки ввода и контексты вывода — reflected, stored и DOM-based, включая письма и экспорты — и покажем, как закрыть.
Читайте также: IDOR на практике, Пентест SPA.