Книга: «Все об уязвимости XSS». Глава 3: Как работает XSS — Анатомия атаки 🔬Контексты выполнения: HTML, JavaScript, атрибуты, URL
Ты нашёл точку входа. Вставил <script>alert(1)</script> . Не сработало. Вставил onerror=alert(1) . Опять мимо. В чём дело? В том, что твой payload — это ключ, а для каждого замка нужен свой ключ. В мире XSS замок — это контекст выполнения. Это то место в коде страницы, куда попадает твой ввод. Не поняв контекст, ты будешь как мартышка с гранатой — много шума, но цель не поражена. Давай разберёмся, какие бывают контексты и как подбирать под них правильные отмычки.
HTML Context: Классика жанра
Это самый простой и очевидный контекст. Твой ввод вставляется прямо между тегами, в текстовое содержимое.
Пример уязвимого кода:
<div>Результаты поиска: [USER_INPUT]</div>
Браузер читает этот HTML и ждёт, что внутри <div> будет текст. Но если ты вместо текста подсунешь ему теги, он их честно отрендерит.
Твои payload’ы:
• <script>alert(1)</script> Это дедовский метод, который до сих пор работает. Ты просто создаёшь новый HTML-элемент <script> , и браузер послушно выполняет его содержимое. Это альфа и омега XSS.
• <img src=x onerror=alert(1) > Что, если WAF или фильтр режет слово <script> ? Не проблема. Создаём тег <img> с несуществующим источником ( src=x ). Браузер не найдёт картинку и вызовет событие onerror . В обработчик этого события мы и пихаем наш код. Элегантно и эффективно.
• <svg onload=alert(1) > Ещё один мощный вектор. SVG-изображения — это, по сути, XML, который может содержать JavaScript. Событие onload сработает, как только SVG будет загружен в DOM. Фильтры часто пропускают <svg> , считая его безобидной картинкой.
• <iframe src="javascript:alert(1)"> Создаём iframe и в качестве источника указываем javascript: URI. Браузер выполнит код. Этот метод хорош, когда нужно изолировать свой payload от основного DOM.
Суть: В HTML-контексте твоя задача — создать новый тег, который может выполнять JavaScript.
HTML Attribute Context: Игра в кавычки
Здесь твой ввод попадает внутрь атрибута существующего HTML-тега.
Пример уязвимого кода:
<a href="/search?q=[USER_INPUT]">Поиск</a>
или
<input type="text" value="[USER_INPUT]">
Твоя задача — вырваться из атрибута и либо создать новый исполняемый атрибут, либо закрыть текущий тег и создать новый.
Сценарий 1: Атрибут в кавычках ( value="..." )
Ты внутри кавычек. Чтобы вырваться, нужно сначала закрыть их.
Пример: <input value="USER_INPUT">
Payload: "><script>alert(1)</script>
Как это работает:
1. " — закрывает атрибут value . HTML превращается в <input value=""> .
2. > — закрывает тег <input> .
3. <script>alert(1)</script> — создаёт новый тег, который выполняется.
4. Остаток оригинального кода ( "> ) становится мусорным текстом в DOM, но уже никому не мешает.
Альтернативный payload (для URL-атрибутов типа href , src ): javascript:alert(1)
Если ты в атрибуте, который ожидает URL (например, <a href="USER_INPUT"> ), то можно использовать псевдо-протокол javascript: . Пример: <a href="javascript:alert(1)">
Сценарий 2: Атрибут без кавычек ( value=... )
Это подарок судьбы. Если разработчик поленился поставить кавычки, то атрибут заканчивается на первом же пробеле.
Пример: <input value=USER_INPUT>
Payload: onmouseover=alert(1)
Как это работает:
1. Браузер видит <input value=твое_значение onmouseover=alert(1)> .
2. Он считает, что value закончился перед onmouseover .
3. onmouseover=alert(1) становится новым, совершенно валидным атрибутом — обработчиком события.
4. Когда юзер наведёт мышь на инпут, твой код сработает.
Твои лучшие друзья — event handlers:
• onmouseover — срабатывает при наведении мыши.
• onclick — требует клика.
• onerror — для медиа-тегов.
• onfocus — для полей ввода. Можно скомбинировать с autofocus , чтобы сработало сразу: autofocus onfocus=alert(1) .
Суть: В контексте атрибута твоя задача — либо закрыть кавычки и тег, либо (если кавычек нет) просто добавить новый атрибут-событие.
JavaScript Context: Вырвись из строки
Это высший пилотаж. Твой ввод вставляется внутрь существующего тега <script> , обычно в строковую переменную.
Пример уязвимого кода:
<script>
var username = '[USER_INPUT]';
console.log('Welcome, ' + username);
</script>
Здесь <script> не сработает — ты уже внутри скрипта. Задача — сломать синтаксис JavaScript, выйти из текущей строки и выполнить свой код.
Payload: ';alert(1)//
Как это работает:
1. ' — закрывает строку var username = '' .
2. ; — завершает текущую инструкцию ( var username = ''; ). Это важно для синтаксической корректности.
3. alert(1) — твой собственный, новый JavaScript-код.
4. // — комментирует остаток строки. Без этого оригинальная закрывающая кавычка ( ' ) вызвала бы синтаксическую ошибку.
Финальный код, который видит браузер:
<script>
var username = '';alert(1)//';
console.log('Welcome, ' + username);
</script>
Всё, что после // , игнорируется. Профит.
Ещё пример (внутри функции):
<script>
trackEvent('login', '[USER_INPUT]');
</script>
Payload: ');alert(1);('
Разбор:
1. ') — закрывает строку и вызов функции trackEvent .
2. ; — завершает инструкцию.
3. alert(1) — твой код.
4. ;(' — чтобы “съесть” оставшийся '); из оригинального кода. Получается ('); , что является синтаксически валидным (вызов пустой анонимной функции).
Особый случай — template literals:
<script>
var message = `Hello, ${'[USER_INPUT]'}`;
</script>
Payload: ${alert(1)} Здесь даже ничего ломать не надо. Template literals в ES6 позволяют выполнять выражения прямо внутри строки.
Суть: В JS-контексте твоя задача — завершить текущую строку/оператор и вставить свой код, не забыв закомментировать или сбалансировать оставшийся “хвост” оригинального кода, чтобы не было синтаксических ошибок.
URL Context: javascript: и `` спешат на помощь
Здесь твой ввод используется как часть URL. Это часто пересекается с контекстом атрибутов ( href , src , action ).
Пример уязвимого кода:
<script>
location.href = '[USER_INPUT]';
</script>
или
<a href="[USER_INPUT]">Перейти</a>
Твои payload’ы:
• javascript:alert(1) Самый прямой путь. Ты подменяешь протокол (http, https, ftp) на javascript: . Браузер видит это и выполняет следующий за ним код. Этот метод стар как мир, но до сих пор эффективен.
• text/html,<script>alert(1)</script> Более хитрый способ. `` URI позволяет встраивать данные прямо в URL. Здесь мы говорим браузеру: “вот тебе HTML-документ, отрендери его”. И он рендерит наш документ, который состоит всего из одного скрипта. Отлично обходит фильтры, которые ищут только javascript: .
Важный момент — URL-кодирование. Браузеры и серверы могут кодировать спецсимволы в URL. Пробел станет %20 , кавычка — %22 . Иногда это ломает payload, а иногда, наоборот, помогает обойти WAF. Например, javascript:alert(1) может быть закодирован как javascript:%61lert(1) . Экспериментируй.
Контекст решает всё
Прежде чем бросаться payload’ами, остановись и подумай.
1. Посмотри исходный код страницы. Где именно появляется твой ввод?
2. Определи контекст: это голый HTML, атрибут, JS-строка или URL?
3. Подбери правильный ключ:
• HTML -> Внедряй теги ( <script> , <img> , <svg> ).
• Атрибут -> Закрывай кавычки/тег или добавляй обработчики событий.
• JavaScript -> Завершай строку, вставляй свой код и комментируй остаток.
• URL -> Используй javascript: или `` URI.
Понимание контекста — это то, что отличает профессионального эксплойт-разработчика от скрипт-кидди. Ты не просто спамишь payload’ами в надежде, что что-то сработает. Ты анализируешь цель, понимаешь её логику и наносишь точный, выверенный удар. В следующих главах мы углубимся в то, как обходить даже самые хитрые фильтры, комбинируя знания о контекстах. Будет жарко.
