Кратко:
- XSS (Cross-Site Scripting) — это уязвимость, при которой злоумышленник внедряет вредоносный скрипт в веб-страницу, а затем этот скрипт выполняется браузером другого пользователя.
- Существует три основных типа: reflected (отражённый), stored (сохранённый) и DOM-based.
- Чаще всего атака позволяет похитить сессионные cookies, изменить содержимое страницы или выполнять действия от имени пользователя.
- Основная защита — корректное кодирование выходных данных в зависимости от контекста + Content Security Policy.
- XSS до сих пор входит в число самых распространённых проблем веб-приложений по данным OWASP.
- Даже современные фреймворки не спасают, если разработчик обходит их механизмы экранирования.
XSS-атака — это способ заставить браузер пользователя выполнить чужой JavaScript-код в контексте доверенного сайта. Злоумышленник не взламывает сервер напрямую. Он подсовывает скрипт через поле ввода, URL или другой канал, а сайт возвращает этот скрипт как часть своей страницы. Браузер доверяет домену и запускает код.
Именно поэтому XSS работает даже тогда, когда сервер полностью защищён от SQL-инъекций или удалённого выполнения кода. Атака происходит на стороне клиента. На практике я неоднократно видел, как простые поисковые формы без экранирования становились точкой входа для таких атак. Один неверно обработанный параметр — и можно похитить сессию администратора.
Понимать механизм важно и разработчикам, и тем, кто тестирует безопасность, и даже обычным пользователям, которые замечают странное поведение сайтов. Далее разберём, как именно это работает, какие бывают разновидности и что реально помогает защититься.
Как работает XSS-атака
Суть атаки в том, что веб-приложение принимает данные от пользователя и вставляет их в HTML-страницу без надлежащей проверки и кодирования. Браузер видит скрипт, который якобы пришёл из доверенного источника, и выполняет его с правами этого сайта.
Типичный сценарий выглядит так. Злоумышленник находит место, где введённые данные отображаются обратно — поиск, комментарий, имя пользователя, сообщение об ошибке. Он подставляет фрагмент вроде ... или более изощрённый вариант через атрибуты событий. Когда жертва открывает страницу, скрипт выполняется. Он может прочитать cookies, отправить их на сервер атакующего, изменить DOM, подменить формы или выполнить запросы от имени пользователя.
Важный момент: same-origin policy, которую браузеры строго соблюдают, здесь обходится именно потому, что код выполняется «внутри» доверенного происхождения. Именно это делает XSS особенно опасным по сравнению со многими другими клиентскими атаками.
Три основных типа XSS
Классификация, которую используют OWASP и PortSwigger, выделяет три главные формы. Они отличаются местом хранения payload и тем, кто инициирует выполнение.
Reflected XSS (отражённый)
Это самый простой и распространённый вариант. Вредоносный код приходит в HTTP-запросе (обычно через параметр URL или тело формы) и сразу возвращается в ответе сервера без сохранения. Атакующий отправляет жертве специально составленную ссылку. Человек кликает — и скрипт выполняется.
Классический пример — поисковая форма, которая выводит «Вы искали: [введённый текст]». Если текст не экранируется, можно вставить тег script. На практике такие уязвимости часто встречаются на страницах ошибок и в старых админках.
Опасность reflected XSS в том, что её легко распространять через фишинг. Но payload не сохраняется, поэтому атака разовая для каждой жертвы.
Stored XSS (сохранённый или постоянный)
Здесь скрипт сохраняется на сервере — в базе данных, файле, комментарии, профиле пользователя, тикете поддержки. Каждый, кто открывает заражённую страницу, автоматически выполняет payload. Это самый опасный тип, потому что одна успешная запись может поразить сотни или тысячи людей, включая администраторов.
Типичные места: форумы, блоги с комментариями, чаты, системы заявок. Однажды приходилось расследовать случай, когда злоумышленник оставил payload в поле «дополнительная информация» заявки. Когда оператор открывал её в админ-панели — сессия администратора уходила на сторону.
DOM-based XSS
В этом случае сервер может вообще не видеть вредоносный код. Всё происходит на стороне клиента. JavaScript приложения читает данные из URL (location.hash, location.search), postMessage, localStorage или document.referrer и вставляет их в DOM через опасные методы — innerHTML, document.write, eval и т. п.
Серверные фильтры здесь почти не помогают. Атака часто остаётся незамеченной при обычном тестировании запрос-ответ. Именно поэтому DOM XSS требует анализа клиентского кода и отслеживания потока данных от источника до sink.

Сравнение типов XSS
| Тип | Где живёт payload | Кто запускает | Сложность обнаружения | Уровень риска |
|---|---|---|---|---|
| Reflected | В текущем запросе | Жертва кликает по ссылке | Средняя | Средний |
| Stored | В базе / на сервере | Любой, кто открывает страницу | Высокая (нужно проверять сохранение) | Высокий / критический |
| DOM-based | В браузере (источник → sink) | Загрузка страницы с payload в URL | Высокая (нужен анализ JS) | Средний–высокий |
Таблица показывает, почему stored XSS часто считают худшим вариантом. Он работает без дополнительных действий жертвы после того, как payload попал в систему.
Что может сделать злоумышленник через XSS
После успешного выполнения скрипта возможности почти неограниченны в пределах того, что может делать легитимный код сайта:
- Похищение сессионных cookies и токенов (если они не защищены флагом HttpOnly).
- Подмена содержимого страницы — фейковые формы входа, сообщения об «обновлении пароля».
- Выполнение действий от имени пользователя — перевод средств, изменение настроек, удаление данных.
- Кейлоггер или перехват введённых данных.
- Распространение червя, если есть возможность оставлять контент от имени жертвы.
В случаях, когда жертвой становится администратор, атака может перерасти в полный контроль над приложением. Именно поэтому stored XSS в зонах с повышенными привилегиями особенно критичны.
Как защищаться от XSS
Защита строится на нескольких слоях. Один механизм редко бывает достаточным.
Контекстное кодирование выходных данных
Это основной и самый надёжный способ. Данные, которые попадают в HTML, нужно экранировать в зависимости от места вставки:
- В теле HTML — экранировать < > & " '
- В атрибутах — дополнительно учитывать кавычки и специальные символы
- В JavaScript-контексте — использовать правильное JS-экранирование
- В URL — percent-encoding
Большинство современных шаблонизаторов и фреймворков (React, Angular, Vue, Twig, Blade) делают это автоматически, если не обходить их механизмы. Проблема начинается именно тогда, когда разработчик использует dangerouslySetInnerHTML, v-html или аналогичные «обходные пути» без дополнительной санитизации.
Санитизация HTML
Когда нужно разрешать пользователям вводить разметку (комментарии с форматированием, редакторы), используют библиотеки вроде DOMPurify. Они оставляют только безопасные теги и атрибуты. Важно не мутировать результат после санитизации — иначе возможен mutation XSS.
Content Security Policy (CSP)
CSP — мощный дополнительный барьер. Правильно настроенная политика может запретить выполнение инлайн-скриптов и загрузку скриптов из неизвестных источников. Лучший вариант — nonces или hashes вместе со strict-dynamic. CSP не заменяет кодирование, но существенно снижает ущерб даже при наличии уязвимости.
Trusted Types
Современный механизм браузеров, который запрещает опасным sink’ам (innerHTML, eval и т. п.) принимать обычные строки. Данные должны проходить через политику Trusted Types. Это один из самых эффективных способов закрыть DOM-based XSS на уровне платформы.

Типичные ошибки, которые оставляют двери открытыми
По опыту тестирования и разбора инцидентов чаще всего встречаются такие промахи:
- Экранирование только на входе, а не на выходе. Фильтрация «плохих» слов легко обходится.
- Использование blacklist вместо whitelist. Новые векторы появляются постоянно.
- Доверие к данным, которые «уже сохранены в базе». Stored XSS рождается именно здесь.
- Игнорирование DOM-источников — hash, postMessage, document.referrer.
- Загрузка SVG без конвертации в растр. SVG может содержать скрипты.
- Отключение HttpOnly на cookies «для удобства» JavaScript.
Отдельно стоит упомянуть о слепом XSS (blind XSS). Payload сохраняется и выполняется позже в другой части системы — например, в панели администратора или в системе мониторинга. Обнаружить его сложнее, потому что обратной связи нет. Для таких случаев используют специальные инструменты с out-of-band детекцией.
Что делать разработчику прямо сейчас
Если вы поддерживаете существующий проект, начните с аудита мест, где пользовательские данные попадают в ответ. Проверьте все шаблоны, поиск, профили, комментарии, сообщения об ошибках. Включите CSP в режиме report-only и посмотрите, что блокируется. Для новых проектов сразу используйте фреймворки с автоматическим экранированием и не обходите их без веской причины.
Тестировщикам стоит комбинировать ручной анализ с автоматизированными сканерами, но не полагаться только на них. DOM-based и слепые XSS часто ускользают от чисто серверных проверок. Полезные ресурсы для более глубокого изучения — материалы PortSwigger Web Security Academy и шпаргалки OWASP по предотвращению XSS.
XSS не исчезнет в ближайшее время. Пока веб-страницы смешивают данные, разметку и код, эта уязвимость будет оставаться актуальной. Но её можно сделать редкой и малоэффективной, если соблюдать простые, но последовательные правила кодирования и политики безопасности. Проверьте свои формы и шаблоны сегодня — завтра может быть поздно.