PHP

PHP Dawg от Fabricio Rosa Marques

Методы PHP SSRF

Как обходить filter_var (), preg_match () и parse_url ()

Несколько дней назад я прочитал две потрясающие статьи: первую, опубликованную на blackhat.com, « Новая эра SSRF », в которой говорится о SSRF на разных языках программирования, а вторая — красивая статья Positive Technology, названная « PHP Wrapper » о том, как использовать PHP-обертку, чтобы обойти фильтры и вводить санитацию разными способами (вы можете найти обе ссылки ниже).

В этой статье я хочу углубиться в несколько методов SSRF, которые вы можете использовать против PHP-скрипта, который использует фильтры, такие как

filter_var()

или

preg_match()

и получать содержимое HTTP с помощью

curl

или

file

или

file_get_contents()

.

типичная атака SSRF на когти

Согласно OWASP:

При атаке на основе запроса на стороне сервера (SSRF) злоумышленник может злоупотреблять функциональными возможностями на сервере, чтобы читать или обновлять внутренние ресурсы. Злоумышленник может предоставить или изменить URL-адрес, который код, запущенный на сервере, будет считывать или отправлять данные, и, тщательно выбирая URL-адреса, злоумышленник может читать конфигурацию сервера, такую ​​как метаданные AWS, подключаться к внутренним службам, таким как http или выполнять почтовые запросы для внутренних служб, которые не предназначены для раскрытия.

Уязвимый код PHP

Все мои тесты выполняются с использованием PHP 7.0.25 (возможно, когда вы прочитаете этот пост, он будет устаревшим, но все описанные методы должны работать в любом случае):

Используемая версия PHP

Следуя, PHP-скрипт, который я буду использовать для тестов:

Как вы можете видеть, скрипт получает URL-адрес от первого аргумента (он может быть $ _GET или $ _POST в веб-приложении), затем он проверяет URL-адрес с

filter_var()

, чтобы проверить формат URL-адреса. Если все в порядке, он анализирует URL-адрес с помощью

parse_url()

и проверяет, заканчивается ли имя узла запроса

google.com

регулярным выражением, используя

preg_match()

.

Если все в порядке, сценарий делает HTTP-запрос, чтобы получить целевую веб-страницу с помощью

curl

и

print_r()

, чтобы показать тело ответа.

Ожидаемое поведение

Этот PHP-скрипт должен принимать запросы только для имени google.com и отклонять все другие цели. Давайте попробуем:

Попытка запросить страницу google.com

Попробуйте запросить страницу evil.com

До сих пор все звучит хорошо. Первый запрос на google.com был принят, а второй на evil.com был отклонен. Уровень безопасности: 1337+ 🙂

Проверка байпасного URL и регулярное выражение

Как вы можете видеть в моем уродливом PHP-коде, регулярное выражение проверяет, заканчивается ли имя узла запроса

google.com

. Это может показаться затруднительным, но если вы хорошо знаете синтаксис URI RFC, вы должны знать, что точка с запятой и запятая могут быть вашим секретным оружием, чтобы использовать SSRF для удаленной цели.

Многие схемы URL резервируют определенные символы для особого значения: их внешний вид в определенной для схемы части URL имеет обозначенную семантику. Если символ, соответствующий октету, зарезервирован в схеме, октет должен быть закодирован. Символы «;», «/», «?», «:», «@», «=» И «&» могут быть зарезервированы для специального значения в рамках схемы. Никакие другие символы не могут быть зарезервированы в рамках схемы.

Помимо точечных сегментов в иерархических путях, сегмент пути считается непрозрачным по обобщенному синтаксису. В URI, создающих приложения, часто используются зарезервированные символы, разрешенные в сегменте, для разграничения подкомпонентов, специфичных для конкретной схемы или разнесения. Например, зарезервированные символы с запятой («;») и равно («=») часто используются для разграничения параметров и значений параметров, применимых к этому сегменту. Зарезервированный символ запятой («,») часто используется для аналогичных целей.

Например, один производитель URI может использовать сегмент, такой как

name;v=1.1

чтобы указать ссылку на версию 1.1 «name», тогда как другой может использовать сегмент, такой как «name, 1.1», чтобы указать его. Типы параметров могут быть определены с помощью специфичной для схемы семантики, но в большинстве случаев синтаксис параметра специфичен для реализации алгоритма разыменования URI.

Если используется на имя хоста, например

evil.com;google.com

он может быть проанализирован curl или wget, например

hostname: evil.com

и

hostname: evil.com
querystring: google.com

. Давай попробуем:

Попробуйте обходить фильтры, используя google.com

Функция

filter_var()

может анализировать многие типы схем URL. Как вы можете видеть,

filter_var()

отказывается проверять мой запрошенный URL с точкой с запятой на имя хоста и «HTTP» в качестве схемы. Но что, если я изменю схему с http: // на что-то еще?

фильтр обходит с помощью 0 в качестве схемы вместо http

Да! ok: both

filter_var()

и

preg_match()

обойдены, но curl не может получить страницу evil.com еще … Почему? Попробуем использовать синтаксис, который не позволяет,

;google.com

анализируется как часть имени хоста, например, указав порт назначения:

SSRF делает завиток, чтобы запросить evil.com вместо google.com

Бинго! Как вы можете видеть, скручивайтесь, чтобы получить

evil.com

сейчас! Такое же поведение происходит с использованием запятой

,

вместо точки с запятой

;

:

Тот же SSRF с запятой вместо точки с запятой

Функция Elude URL Parsing и SSRF

parse_url()

— это функция PHP, которая анализирует URL-адрес и возвращает ассоциативный массив, содержащий любой из различных компонентов URL-адреса, которые присутствуют. Эта функция не предназначена для проверки данного URL-адреса , но только разбивает ее на перечисленные выше части. Частичные URL-адреса также принимаются,

parse_url()

пытается наилучшим образом проанализировать их.

Один из моих любимых методов, чтобы обойти регулярное выражение в таком сценарии, — это преобразовать часть строки в переменную. Эта работа, когда результат оценивается Bash. Например:

Использование «синтаксиса переменной Bash» для обхода фильтров и использования SSRF

С помощью этой техники я делаю bash для разбора

$google

как пустой переменной и завитки, чтобы запросить

evil<empty>.com

. Круто, не так ли? 🙂

Это происходит только в синтаксисе curl. Фактически, как показано на скриншоте выше, имя хоста, обработанное

parse_url()

по-прежнему является

evil <strong class="markup--strong markup--p-strong">$google</strong> .com

. Переменная $ google пока не интерпретируется. Только когда с функцией

exec()

скрипт использует

$r['host']

для создания завихряющегося HTTP-запроса, Bash преобразует его в пустую переменную.

Очевидно, что эта работа на всякий случай, если PHP-скрипт использует функцию

exec()

или

system()

для вызова системной команды, такой как

curl

,

wget

или что-то в этом роде.

Данные обертки: // и XSS для победы

Другой пример PHP-кода с использованием

file_get_contents()

вместо вызова

curl

из

system()

или

exec()

:

Как вы видите,

file_get_contents()

использует переменную raw аргумента после проверки ее с помощью той же методики, описанной ранее. Попробуем изменить тело ответа, введя некоторый текст, например « I Love PHP »:

Попытка контролировать тело ответа

Не разрешено 🙁

parse_url()

установить текст как хост запроса и правильно отклонить его для « недопустимого хоста ». Не отчаивайтесь! Мы можем что-то сделать, мы можем попытаться «ввести» что-то в mime- введите часть URI … потому что в этом случае PHP не заботится о типе mime … кого это волнует?

Injected «Я люблю PHP» в тело ответа

Отсюда в XSS есть кусок торта …

Easy XSS с использованием описанной выше методики

Это все люди, так долго и спасибо за всех рыб!