XSS: межсайтовый скриптинг - как JavaScript становится оружием

XSS: межсайтовый скриптинг - как JavaScript становится оружием

Когда сайт начинает выполнять чужой код

Веб-приложение может выглядеть совершенно нормально: форма поиска, личный кабинет, комментарии, профиль пользователя и привычный интерфейс. Но где-то внутри страницы оказывается небольшая ошибка, из-за которой данные посетителя попадают в HTML без правильной обработки. В этот момент обычная строка перестаёт быть просто текстом и превращается в часть страницы.

Если браузер воспринимает полученные данные как JavaScript, возникает Cross-Site Scripting, или XSS. Злоумышленник добивается того, чтобы его код выполнялся не на собственном компьютере, а в браузере другого пользователя и внутри доверенного сайта.

Именно это делает XSS опасной. Жертва видит знакомый домен, действующий сертификат и настоящий интерфейс приложения. Код при этом запускается в контексте страницы, которой пользователь уже доверяет.

Скрипт может изменять содержимое сайта, отправлять запросы от имени текущего пользователя, читать доступные странице данные и подменять элементы интерфейса. Последствия зависят не только от самой уязвимости, но и от того, какие возможности предоставляет приложение авторизованному пользователю.

Небольшая ошибка при выводе текста способна превратиться в полноценную точку атаки.

Браузер не понимает намерений разработчика

Для человека разница между текстом и кодом очевидна. Если пользователь написал комментарий, разработчик ожидает увидеть на странице именно комментарий. Браузер рассуждает иначе. Он получает последовательность символов и разбирает её в соответствии с правилами HTML.

Допустим, приложение формирует результат поиска следующим образом:

<p>Результаты по запросу: пользовательский_текст</p>

Пока вместо пользовательский_текст подставляются обычные слова, всё работает ожидаемо. Но если данные содержат HTML-разметку и приложение выводит их без экранирования, браузер начинает воспринимать введённые символы как часть документа.

Простейшая безопасная демонстрация может выглядеть так:

<script>alert(1)</script>

Здесь alert(1) не наносит вреда. Он лишь показывает, что браузер выполнил внедрённый JavaScript.

Проблема находится не в функции alert(), а в самом факте выполнения чужого кода. Если вместо демонстрационного вызова подставить другую логику, поведение страницы изменится уже не так безобидно.

Поэтому подтверждение XSS не требует создания опасной нагрузки. Для тестирования достаточно показать, что контролируемый JavaScript действительно выполняется в нужном контексте.

XSS работает с правами самой страницы

JavaScript, внедрённый через XSS, не получает абсолютного контроля над компьютером пользователя. Он остаётся ограничен возможностями браузера, политикой одного источника и настройками самого сайта.

Но внутри уязвимого приложения такой код может делать многое.

Если обычный JavaScript страницы способен отправлять запросы к API, изменять профиль, публиковать сообщения или выполнять административные действия, внедрённый скрипт может попытаться использовать те же функции. Браузер автоматически добавляет к запросам доступные учётные данные, поэтому сервер иногда воспринимает действия как легитимные.

Особенно опасна XSS в административных интерфейсах. Скрипт, который выполняется у обычного посетителя, может иметь ограниченный эффект. Тот же код, открытый администратором, оказывается в значительно более привилегированном контексте.

Именно поэтому влияние XSS нельзя оценивать только по месту обнаружения. Важно понимать, кто увидит уязвимое содержимое, какие права будут у этого пользователя и какие действия разрешает приложение.

Почему название говорит о межсайтовом скриптинге

Термин Cross-Site Scripting появился в период, когда атаки часто строились вокруг взаимодействия нескольких сайтов. Один ресурс внедрял скрипт, который выполнялся в контексте другого.

Со временем понятие стало шире. Сегодня XSS называют любую ситуацию, в которой недоверенные данные превращаются в исполняемый клиентский код внутри веб-приложения.

Классическое сокращение CSS использовать было неудобно, поскольку оно уже обозначало Cascading Style Sheets. Поэтому для уязвимости закрепилось название XSS.

Несмотря на возраст термина, сама проблема никуда не исчезла. Современные frontend-фреймворки автоматически экранируют многие значения, но разработчики продолжают использовать прямую вставку HTML, сторонние шаблонизаторы, небезопасные функции и сложные клиентские приложения.

Чем больше данных обрабатывается в браузере, тем больше мест, где текст может случайно превратиться в код.

Хранимая XSS остаётся на сервере

Stored XSS возникает, когда вредоносные данные сохраняются приложением и позже вставляются в страницу.

Источником может стать комментарий, имя пользователя, описание товара, сообщение поддержки, запись в журнале или содержимое профиля. Пользователь отправляет данные один раз, после чего сервер помещает их в базу.

При каждом последующем открытии страницы приложение извлекает сохранённое значение и выводит его посетителям. Если обработка отсутствует или выполнена неправильно, код запускается у каждого пользователя, который увидит заражённый элемент.

Такой сценарий особенно опасен из-за масштаба. Жертве не обязательно переходить по специально подготовленной ссылке. Достаточно открыть обычный раздел сайта.

Представим внутреннюю систему обращений. Пользователь отправляет сообщение, а сотрудник поддержки просматривает его в административной панели. Если текст обращения попадает в HTML без безопасного кодирования, XSS выполняется уже в браузере сотрудника.

В этот момент уязвимость обычной формы начинает затрагивать привилегированную часть системы.

Хранимая XSS требует внимательного изучения всех мест, где пользовательские данные сохраняются и затем отображаются в другом контексте. Проверять нужно не только публичные страницы, но и панели модерации, уведомления, историю изменений и экспортированные отчёты.

Отражённая XSS живёт внутри запроса

Reflected XSS не обязательно сохраняется в базе данных. Вредоносное значение передаётся в HTTP-запросе, а сервер сразу возвращает его внутри ответа.

Типичный пример - страница поиска. Пользователь открывает адрес с параметром:

/search?q=javascript

Сервер выводит запрос на странице:

<h1>Результаты поиска по запросу: javascript</h1>

Если значение q вставляется без экранирования, специально сформированная строка может изменить структуру HTML.

Отражённая XSS часто требует взаимодействия с жертвой. Пользователь должен открыть подготовленную ссылку, отправить форму или выполнить другое действие. Поэтому такие атаки нередко маскируют под обычные ссылки на поиск, документ или страницу авторизации.

Домен в адресной строке при этом остаётся настоящим. Это повышает доверие пользователя и усложняет визуальное распознавание подмены.

Сам вредоносный код может находиться прямо в URL или собираться из нескольких параметров. Иногда приложение обрабатывает значение на сервере, а затем дополнительно изменяет его в браузере, создавая сложную цепочку преобразований.

Для проверки отражённой XSS важно увидеть, где именно введённая строка появляется в ответе и в каком контексте браузер её разбирает.

DOM XSS может вообще не затрагивать сервер

DOM-based XSS возникает в клиентском JavaScript. Сервер способен вернуть полностью безопасную страницу, а уязвимость появится позже, когда frontend прочитает данные из URL, DOM, локального хранилища или другого источника и небезопасно вставит их в документ.

Простой пример:

const message = location.hash.slice(1);
document.querySelector("#output").innerHTML = message;

Если пользователь откроет страницу с определённым значением после символа #, браузер передаст его в location.hash. Скрипт затем вставит строку через innerHTML.

Фрагмент URL после # обычно не отправляется серверу. Поэтому серверные журналы и фильтры могут вообще не увидеть опасное значение.

Уязвимость существует полностью на стороне клиента.

Если вместо innerHTML использовать безопасное текстовое свойство, браузер не станет разбирать значение как HTML:

const message = location.hash.slice(1);
document.querySelector("#output").textContent = message;

Внешне разница между innerHTML и textContent кажется небольшой. С точки зрения безопасности она принципиальна. Первая операция создаёт HTML-структуру, вторая выводит данные как обычный текст.

DOM XSS особенно часто встречается в одностраничных приложениях, где большая часть навигации и отрисовки выполняется JavaScript без повторной загрузки страницы.

Источник и приёмник определяют уязвимость

При анализе DOM XSS удобно мыслить двумя понятиями: source и sink.

Source - источник недоверенных данных. Это может быть location.search, location.hash, document.referrer, postMessage, cookies, локальное хранилище или ответ API.

Sink - операция, способная превратить данные в код или HTML. К опасным приёмникам относятся innerHTML, outerHTML, document.write(), eval(), Function() и некоторые варианты динамической вставки.

Сама по себе строка из URL ещё не создаёт XSS. Уязвимость появляется, когда недоверенное значение проходит от источника до опасного приёмника без корректной обработки.

Например:

const value = new URLSearchParams(location.search).get("name");
document.querySelector(".profile").innerHTML = value;

Параметр name является источником, а innerHTML - приёмником.

Исправленный вариант может использовать textContent:

const value = new URLSearchParams(location.search).get("name");
document.querySelector(".profile").textContent = value;

Такой подход помогает анализировать большие клиентские приложения. Вместо случайного просмотра тысяч строк исследователь ищет источники пользовательских данных, опасные операции и пути между ними.

Контекст важнее самой строки

Одинаковая нагрузка не работает во всех местах. Значение может попадать внутрь HTML-текста, атрибута, JavaScript-строки, CSS или URL. Каждый контекст имеет собственные правила разбора.

Рассмотрим несколько вариантов:

<div>ПОЛЬЗОВАТЕЛЯ</div>
<input value="ПОЛЬЗОВАТЕЛЯ">
<script>
const name = "ПОЛЬЗОВАТЕЛЯ";
</script>
<a href="ПОЛЬЗОВАТЕЛЯ">Открыть</a>

Во всех случаях приложение вставляет пользовательские данные, но браузер воспринимает их по-разному. В одном месте значение находится между HTML-тегами, в другом - внутри атрибута, в третьем - внутри JavaScript.

Поэтому защита не сводится к удалению символов < и >. Данные должны кодироваться в соответствии с конкретным местом вставки.

HTML-контекст требует HTML-экранирования. Атрибуты требуют отдельной обработки и безопасного заключения в кавычки. JavaScript-контекст должен избегать прямого формирования кода из строк. URL необходимо проверять как URL, а не как произвольный текст.

Ошибки начинаются тогда, когда разработчик пытается использовать один универсальный фильтр для всех случаев.

Почему чёрные списки быстро ломаются

Один из самых распространённых способов защиты выглядит логично: найти слово script и удалить его из пользовательского ввода.

На практике такой фильтр почти всегда оказывается слабым.

JavaScript может выполняться не только внутри тега <script>. Браузер поддерживает обработчики событий, различные элементы HTML и множество способов изменения DOM. Кроме того, фильтр может учитывать один регистр, одно написание или только одну последовательность символов.

Даже если удалить несколько известных конструкций, остаются другие варианты. Чем больше исключений добавляется, тем сложнее становится фильтр и тем выше вероятность пропустить необычный синтаксис.

Главная проблема чёрного списка заключается в попытке перечислить всё опасное. Браузерный синтаксис слишком широк, а контексты вывода слишком разнообразны.

Надёжная защита строится иначе. Приложение должно заранее определить, какой тип данных ожидается, и безопасно вывести его в нужном контексте.

Если поле должно содержать имя пользователя, ему не требуется HTML. Если редактор действительно позволяет форматирование, необходимо использовать проверенную библиотеку санитизации и разрешать только ограниченный набор элементов и атрибутов.

Валидация и экранирование не заменяют друг друга

Валидация проверяет, соответствует ли значение ожидаемому формату. Если приложение ждёт число, дата или адрес электронной почты, данные можно проверить до обработки.

Экранирование отвечает за безопасный вывод. Оно превращает специальные символы в представление, которое браузер воспринимает как текст.

Допустим, пользователь ввёл:

<b>Ivan</b>

При безопасном HTML-экранировании страница покажет символы <b>Ivan</b>, а не создаст жирный текст.

Валидация может запретить HTML полностью, но не всегда это возможно. Поле комментария поддерживает почти любой текст, включая угловые скобки. В таком случае главное значение приобретает безопасный вывод.

Санитизация используется, когда приложению действительно нужно принимать часть HTML. Например, редактор статьи может разрешать абзацы, ссылки и выделение текста, но запрещать скрипты, обработчики событий и опасные URL-схемы.

Каждый механизм решает свою задачу. Валидация ограничивает формат, экранирование делает вывод безопасным, а санитизация очищает разрешённую разметку.

Почему фильтровать нужно не только на входе

Иногда разработчики очищают значение при сохранении в базу и считают проблему решённой. Такой подход создаёт несколько сложностей.

Одни и те же данные могут использоваться в разных контекстах. Имя пользователя выводится в HTML, подставляется в JavaScript, экспортируется в CSV и добавляется в электронное письмо. Обработка, безопасная для одного контекста, не обязательно подходит для другого.

Кроме того, правила приложения меняются. Сегодня поле допускает только обычный текст, а через год его начинают отображать внутри атрибута или клиентского шаблона.

Поэтому основной принцип заключается в кодировании на выходе. Приложение должно безопасно преобразовать данные непосредственно перед вставкой в конкретный контекст.

Проверка на входе всё равно нужна. Она помогает поддерживать качество данных и отсеивать явно недопустимые значения. Но полагаться только на неё нельзя.

Надёжная система предполагает, что сохранённые данные могут быть недоверенными, и обрабатывает их каждый раз перед выводом.

Современные фреймворки помогают, но не гарантируют безопасность

React, Vue, Angular и другие современные инструменты по умолчанию экранируют значения, которые выводятся через стандартные шаблоны.

Например, React обычно отображает строку как текст:

<div>{userInput}</div>

Даже если userInput содержит HTML, React не превращает его автоматически в элементы страницы.

Проблемы появляются, когда разработчик сознательно обходит защиту. В React существует dangerouslySetInnerHTML, во Vue - директива v-html, а в других фреймворках встречаются похожие механизмы.

Они нужны для ситуаций, когда приложение должно отобразить готовый HTML. Одновременно они передают ответственность за безопасность разработчику.

Если в такую функцию попадают пользовательские данные без санитизации, встроенное экранирование перестаёт помогать.

Уязвимости также возникают за пределами шаблонов. Код может напрямую обращаться к DOM, использовать сторонний плагин, небезопасно работать с URL или передавать строку в eval().

Фреймворк снижает вероятность случайной XSS, но не заменяет понимание браузерной модели безопасности.

CSP ограничивает последствия ошибки

Content Security Policy, или CSP, позволяет сайту указать, какие источники скриптов, стилей и других ресурсов разрешены браузеру.

Политика передаётся через HTTP-заголовок:

Content-Security-Policy: default-src 'self'; script-src 'self'

В упрощённом виде такая настройка разрешает загружать скрипты только с текущего сайта.

Более строгая конфигурация может использовать nonce. Сервер создаёт случайное значение, добавляет его в заголовок и указывает в разрешённых тегах script:

Content-Security-Policy: script-src 'nonce-r4nd0m'
<script nonce="r4nd0m">
    console.log("Разрешённый код");
</script>

Скрипт без правильного nonce браузер выполнять не должен.

CSP способна значительно уменьшить последствия некоторых XSS, особенно если запрещены inline-скрипты, eval() и загрузка кода с произвольных доменов.

Однако политика не исправляет саму уязвимость. Если приложение вставляет опасный HTML, ошибка остаётся. Неправильно настроенная CSP может разрешать слишком много источников, использовать небезопасные исключения или обходиться через уже доверенные скрипты.

Поэтому CSP рассматривают как дополнительный уровень защиты, а не как замену безопасному выводу данных.

HttpOnly защищает cookies не от всей XSS

Для сессионных cookies часто устанавливают флаг HttpOnly. Он запрещает JavaScript читать значение через document.cookie.

Это важная мера. Даже если XSS выполняется на странице, напрямую получить такую cookie из JavaScript не получится.

Но сама сессия продолжает работать. Браузер автоматически прикладывает cookie к запросам на сайт. Внедрённый скрипт может попытаться выполнять доступные действия от имени пользователя, даже не зная значения сессионного идентификатора.

Флаг HttpOnly уменьшает один из рисков, но не нейтрализует XSS целиком.

Дополнительную защиту дают Secure, SameSite, короткое время жизни сессии, повторное подтверждение важных операций и строгая проверка полномочий на сервере.

Ни один отдельный флаг не заменяет устранение причины уязвимости.

XSS нельзя искать одной строкой

Демонстрационная нагрузка <script>alert(1)</script> известна почти каждому, кто изучал веб-безопасность. Она полезна для объяснения идеи, но плохо подходит как универсальный тест.

Современное приложение может удалить тег script, но оставить другой опасный контекст. Значение может попадать внутрь атрибута, JavaScript-строки или DOM-операции. Иногда браузер исправляет повреждённую разметку неожиданным образом и создаёт исполняемую конструкцию там, где разработчик её не ожидал.

Поэтому поиск XSS начинается не с перебора случайных нагрузок, а с наблюдения за тем, как данные проходят через приложение.

Исследователь вводит уникальную безопасную строку, находит её в ответе или DOM и определяет контекст. Затем проверяет, какие символы изменяются, удаляются или экранируются.

Если значение попадает в HTML как текст, анализируется HTML-кодирование. Если оно оказывается внутри атрибута, проверяются кавычки и структура тега. Если строку обрабатывает клиентский JavaScript, изучается путь от источника до приёмника.

Такой подход даёт больше информации, чем десятки автоматических попыток.

Вручную видно то, что пропускает сканер

Автоматические сканеры умеют находить часть отражённых и хранимых XSS. Они отправляют тестовые значения, ищут их в ответе и проверяют известные контексты.

Но приложение может требовать сложную последовательность действий. Сначала пользователь создаёт объект, затем администратор подтверждает его, после чего значение появляется на другой странице. Автоматический сканер не всегда понимает такую логику.

DOM XSS тоже способна зависеть от действий браузера, маршрутизации одностраничного приложения, сообщений postMessage или данных локального хранилища.

Ручное тестирование помогает увидеть полный путь данных. Исследователь отслеживает запросы через Burp Suite или OWASP ZAP, изучает ответы, открывает инструменты разработчика и наблюдает за изменениями DOM.

Автоматизация полезна для покрытия большого количества параметров, но подтверждение уязвимости и оценка влияния остаются ручной работой.

Сканер может показать, что значение отражается. Только исследователь определит, превращается ли оно в исполняемый код и кто окажется под угрозой.

innerHTML не всегда означает уязвимость

Опасные функции полезно искать во время аудита, но одно их наличие ещё не доказывает XSS.

Например:

element.innerHTML = "<strong>Готово</strong>";

Здесь в innerHTML передаётся постоянная строка, которую контролирует разработчик. Недоверенные данные отсутствуют.

Другой пример:

element.innerHTML = sanitizeHtml(userInput);

Без изучения функции sanitizeHtml() невозможно определить, безопасен ли код. Она может использовать надёжную библиотеку, а может удалять только слово script.

Главный вопрос всегда один: может ли пользователь контролировать данные, которые достигают опасной операции, и проходят ли они корректную обработку?

Анализ источников и приёмников помогает избежать ложных находок. Вместо отчёта со списком всех innerHTML специалист показывает конкретную цепочку данных, воспроизводимое выполнение и понятное влияние.

eval() превращает строки в программу

Функция eval() выполняет строку как JavaScript:

eval("console.log('test')");

Если содержимое строки полностью контролируется разработчиком, немедленной XSS может не быть. Но сама конструкция создаёт риск, поскольку любое попадание пользовательских данных превращает их из текста в код.

Особенно опасно формировать выражение через конкатенацию:

const value = getUserInput();
eval("showMessage('" + value + "')");

Кавычки и специальные символы могут изменить структуру итоговой программы.

В большинстве случаев eval() не нужен. Обычные функции, объекты и методы доступа к свойствам позволяют решить задачу без динамического выполнения кода.

Отказ от eval() упрощает анализ, улучшает совместимость со строгой CSP и уменьшает количество мест, где данные могут стать исполняемыми инструкциями.

Тот же принцип относится к конструктору Function(), строковым аргументам setTimeout() и другим механизмам динамического выполнения.

XSS часто становится частью цепочки

Отдельная XSS может выглядеть ограниченной. Например, код выполняется только в разделе профиля и только после перехода по специальной ссылке.

Но атакующий оценивает не одну страницу, а всю систему.

Скрипт может взаимодействовать с внутренним API, доступным только авторизованному пользователю. Он может изменять настройки аккаунта, создавать новые объекты, отправлять сообщения или изучать данные, которые видит жертва.

Если в приложении есть слабый контроль доступа, XSS помогает использовать его через браузер привилегированного пользователя. Если важные действия не требуют повторного подтверждения, скрипт способен выполнять их незаметно.

Особенно серьёзные последствия возникают при сочетании хранимой XSS с административной панелью. Пользователь сохраняет вредоносное значение, администратор открывает страницу, после чего код работает уже в его контексте.

Поэтому влияние описывают не словами «выполняется JavaScript», а конкретным сценарием. Кто должен открыть страницу? Какие права у жертвы? Какие действия можно выполнить? Требуется ли дополнительное взаимодействие?

Именно эти вопросы определяют реальную критичность.

XSS не равна CSRF

XSS и CSRF иногда приводят к похожему результату: приложение выполняет действие от имени пользователя.

При CSRF браузер отправляет поддельный запрос с учётными данными жертвы, но атакующий код обычно находится на другом сайте. Защита строится вокруг CSRF-токенов, проверки источника запроса и настроек cookies.

При XSS скрипт выполняется внутри самого уязвимого приложения. Он может читать доступный DOM, взаимодействовать с JavaScript страницы и получать токены, которые встроены в интерфейс.

Поэтому XSS нередко обходит часть CSRF-защиты. Если токен доступен клиентскому коду, внедрённый скрипт может использовать его так же, как обычное приложение.

Защита от CSRF остаётся необходимой, но она не исправляет XSS. Эти уязвимости требуют разных механизмов защиты.

Опасность определяется не эффектностью демонстрации

В отчётах XSS иногда показывают через изменение фона страницы, всплывающее окно или замену заголовка. Такая демонстрация понятна визуально, но почти ничего не говорит о реальном риске.

Гораздо важнее показать границы доступа.

Если скрипт выполняется только у самого пользователя, который ввёл значение, возможно, речь идёт о self-XSS. Если значение сохраняется и открывается модератором, влияние значительно выше. Если CSP блокирует выполнение, а найденная строка создаёт только HTML без JavaScript, вывод будет другим.

Хорошее подтверждение использует минимальную безопасную нагрузку и подробно описывает контекст. Исследователь фиксирует источник данных, место вывода, условия выполнения и категории пользователей, которые могут пострадать.

Цель пентеста заключается не в создании наиболее впечатляющего эффекта, а в точной оценке уязвимости.

Self-XSS требует отдельной оценки

Иногда код выполняется только после того, как пользователь самостоятельно вставил его в консоль браузера или изменил собственные данные сложным способом.

Такие случаи называют self-XSS. Обычно они не позволяют атаковать другого человека без дополнительной социальной инженерии.

Само выполнение JavaScript ещё не означает полноценную уязвимость приложения. Нужно понять, может ли злоумышленник доставить нагрузку жертве без доступа к её браузеру и заставить код выполниться в обычном сценарии.

Однако self-XSS нельзя автоматически игнорировать. Если приложение позволяет превратить личное значение в публичное, поделиться подготовленной ссылкой или перенести данные другому пользователю, ограниченная проблема может стать настоящей XSS.

Оценка всегда зависит от пути распространения.

Безопасное тестирование важнее сложной нагрузки

Для подтверждения XSS обычно достаточно безобидного действия: показать всплывающее окно, изменить заранее выбранный элемент или записать значение в консоль.

Не требуется собирать личные данные, вмешиваться в чужие аккаунты или отправлять информацию на внешние серверы.

В согласованном пентесте заранее определяют допустимые действия, тестовые учётные записи и ограничения. Хранимую нагрузку после проверки необходимо удалить, чтобы она случайно не выполнилась у реального пользователя.

Особая осторожность нужна при тестировании административных панелей, форм поддержки и публичных комментариев. Один сохранённый фрагмент может повлиять на всех посетителей страницы.

Качественный тест подтверждает риск и при этом не создаёт новый инцидент.

Защита начинается с правильного вывода

Самая надёжная основа защиты от XSS - не позволять недоверенным данным становиться кодом.

Обычный текст следует выводить через безопасные методы и шаблонизаторы с автоматическим экранированием. Для клиентского JavaScript предпочтительнее textContent, а не innerHTML.

Если приложению нужен HTML, его необходимо очищать проверенной библиотекой с ограниченным списком разрешённых элементов и атрибутов. Создавать собственный фильтр на регулярных выражениях опасно, поскольку браузерный синтаксис слишком сложен.

Динамическое выполнение строк через eval() и Function() лучше исключить. CSP должна ограничивать источники скриптов и запрещать небезопасные inline-конструкции. Cookies сессии получают флаги HttpOnly, Secure и подходящий режим SameSite.

Но главное правило остаётся прежним: пользовательские данные должны оставаться данными.

Чем меньше мест, где приложение вручную собирает HTML и JavaScript из строк, тем меньше поверхность для XSS.

Почему XSS продолжает появляться

Межсайтовый скриптинг существует почти столько же, сколько динамические веб-приложения. За это время появились автоматическое экранирование, CSP, безопасные шаблонизаторы и подробные рекомендации.

Тем не менее XSS продолжает встречаться в новых проектах.

Причина заключается в сложности современных приложений. Данные проходят через сервер, базу, API, frontend-фреймворк и сторонние библиотеки. Значение может быть безопасно обработано на одном этапе, а затем декодировано или вставлено в другой контекст.

Разработчик использует innerHTML ради удобства, подключает устаревший редактор или доверяет данным из внутреннего API. Приложение считает источник надёжным, хотя пользователь способен повлиять на него через другую функцию.

Проблему создаёт не одна конкретная технология, а нарушение границы между текстом и кодом.

Пока приложения принимают пользовательские данные и формируют из них страницы, риск XSS будет сохраняться.

Как устроен модуль «XSS: Межсайтовый скриптинг» в Kraken Academy

В Kraken Academy модуль начинается не с запоминания набора нагрузок, а с понимания того, как браузер разбирает HTML и почему обычная строка внезапно становится исполняемым кодом.

Пользователь изучает отражённую, хранимую и DOM-based XSS, наблюдает за движением данных от источника до точки вывода и учится определять контекст внедрения. Отдельное внимание уделяется HTML, атрибутам, клиентскому JavaScript и опасным операциям вроде innerHTML и eval().

Во время практики необходимо не просто вызвать демонстрационное окно, а понять причину выполнения. Где приложение потеряло контроль над данными? Почему фильтр не помог? Как браузер преобразовал итоговую разметку? Кто может столкнуться с нагрузкой и какие права будут у этого пользователя?

После обнаружения уязвимости рассматривается защита. Пользователь сравнивает экранирование, валидацию и санитизацию, исправляет небезопасный вывод и изучает влияние Content Security Policy.

В модуле предусмотрено четыре практических задания. Они позволяют самостоятельно пройти путь от поиска точки внедрения до исправления уязвимости в контролируемой среде.

Главная задача модуля - научить видеть XSS не как строку с alert(1), а как нарушение границы между недоверенными данными и исполняемым кодом.

JavaScript становится оружием только из-за ошибки приложения

Сам по себе JavaScript не является угрозой. На нём работает интерфейс сайта, отправляются запросы, проверяются формы и строятся современные веб-приложения.

Опасность появляется тогда, когда приложение позволяет постороннему человеку управлять кодом, который выполнит браузер другого пользователя.

XSS начинается с небольшой ошибки: неправильного вывода строки, небезопасного DOM-метода, устаревшего шаблона или слишком доверчивого фильтра. Но последствия зависят от всего приложения вокруг этой ошибки.

Чтобы находить такие уязвимости, недостаточно знать несколько популярных нагрузок. Нужно понимать HTML-парсер, JavaScript, DOM, HTTP и путь пользовательских данных через систему.

Браузер выполняет именно тот документ, который получил. Он не знает, какой фрагмент создал разработчик, а какой подставил злоумышленник. Провести эту границу обязано само приложение.

Следующий шаг - практика

Эта статья - только начало. Настоящие навыки появляются тогда, когда знания превращаются в практику. В Kraken Academy мы собрали полноценные курсы по информационной безопасности с лабораториями, практическими заданиями и пошаговыми образовательными треками - от первых шагов до уровня специалиста.

Начать обучение

Читать далее

Когда котику делать нечего он поднимает свою лабораторию

Когда котику делать нечего он поднимает свою лабораторию

❗Важно: Материал опубликован исключительно в образовательных целях и предназначен для изучения принципов работы технологий, методов защиты и проведения легального тестирования безопасности. Любые проверки, сканирование, эксплуатация уязвимостей или иные действия в отношении информационных систем допускаются только при наличии явного разрешения их владельца или в специально созданной лабораторной среде. Автор и администрация

Иллюзия безопасности: 5 мифов о кибербезопасности, в которые до сих пор верит топ-менеджмент

Иллюзия безопасности: 5 мифов о кибербезопасности, в которые до сих пор верит топ-менеджмент

В корпоративном управлении существует странный парадокс. Руководители тщательно считают финансовые риски, проверяют контрагентов, страхуют имущество, создают резервы и заранее обсуждают, что произойдёт при падении продаж или потере крупного клиента. Но как только разговор переходит к кибербезопасности, та же логика часто исчезает. Вместо оценки вероятности, ущерба и готовности к восстановлению появляются

Шпионаж из розетки: как данные могут покинуть компьютер через кабель питания

Шпионаж из розетки: как данные могут покинуть компьютер через кабель питания

В кибербезопасности принято защищать очевидные каналы связи. Компании устанавливают межсетевые экраны, шифруют трафик, контролируют USB-устройства, отключают беспроводные интерфейсы и физически изолируют особенно важные системы от интернета. Кажется, что компьютер, не подключённый к локальной сети и внешним сервисам, уже не способен передать данные наружу. Однако у любого работающего устройства остаются

Тестовое с сюрпризом: как фейковые рекрутеры воруют пароли и доступы

Тестовое с сюрпризом: как фейковые рекрутеры воруют пароли и доступы

Представьте, в данный момент вы ищете работу или открыты к предложениям. В известных сервисах по поиску работы или на почту приходит письмо от приятного HR-специалиста. Вакансия — сказка, вилка — выше рынка, требования идеальные. Вам предлагают сделать небольшое тестовое задание, чтобы оценить навыки. Вы скачиваете архив, запускаете файл… и через пять