26 апреля, 2024

Обходим черный список, защищающий от SSRF

Давайте представим себе типичную ситуацию: есть веб-приложение, которое должно уметь брать какой-то контент с других серверов, но, что самое опасное, путь до контента задается пользователем. Атакующий имеет потенциал для проведения SSRF-атак. Даже если предположить, что мы можем использовать только протоколы HTTP и HTTPS, открывается немалый простор для действий: от простейших (можно узнать IP хостов во внутренней сети и открытые порты на них) до хитрых махинаций, которые в определенных ситуациях могут приводить к RCE.

Казалось бы, простейшее решение — вынести веб-приложение в специальную DMZ или, наоборот, полностью перенести в интернет. Во втором случае атакующий может попытаться проводить SSRF, но к внутренним ресурсам доступа не получит. Но, во-первых, современные веб-приложения обычно являют собой многокомпонентную систему (хотя бы веб-сервер и СУБД) с распределением нагрузки (серверов больше, чем один) и системами мониторинга. Все это расширяет простор для атаки. Во-вторых, есть различные локальные сервисы, которые висят на loopback-интерфейсе (а-ля 127.0.0.1), который часто рассматривается как «доверенный» (к нему же можно подключиться только с того же хоста).

Разработчики в итоге приходят к выводу, что давать доступ нужно ко всему, кроме определенного набора мест. Классический способ реализации — это черные списки. Создается набор регулярных выражений, по которому и проверяют полученный от пользователя URL перед инициализацией запроса. Наше же дело — попытаться обойти эти блек-листы. Вот несколько вариантов путей обхода.

Обход регекспа.

Составить правильное регулярное выражение, если возможностей много, — не самая простая задача. Чтобы обойти черный список, нужно для начала вспомнить, как составляется URL. Вот общая схема.

scheme://[user:password@]domain:port/path?query_string#fragment_id
Самое важное то, что перед именем хоста мы можем указать через двоеточие логин и пароль, а далее поставить «собаку». В этом случае мы говорим, что хотим аутентифицироваться на ресурсе. Это не какая-то хитрая фича, а изначальная задумка, и поддерживается она в том или ином виде везде.

Читать также:  Чем различаются операционные системы Linux и Unix

Возьмем адрес safecurl.fin1te.net. Вот примеры того, как его можно спрятать в URL.

http://validurl.com#user:[email protected]
Парсер увидит validurl.com и даст добро, но тот же curl посчитает кредами все, что идет до @. Если точнее, то validurl.com#user — это имя пользователя.

Вот еще один пример.

http://user:pass@safecurl.[email protected]/
Парсер клюнет на последнее значение, а curl использует центральное (safecurl.fin1te.net), так как все перед первой «собакой» считается кредами, но то, что после знака вопроса, — уже часть query_string. На отсутствие закрывающего слеша curl внимания не обратит.

Постатака.

Мы можем предоставлять корректный URL, но тем или иным способом менять точку для конечного подключения. Например, можно указать корректное доменное имя нашего сайта, но резолвиться имя будет в 127.0.0.1 или другой IP-адрес внутренней сети. И так как реально подключение идет по IP, мы обходим черный список. Еще можно использовать редиректы. Указываем имя своего хоста, а на нем ставим редирект на другой хост, который мы хотим атаковать.

Удар в незащищенное место.

На самом деле специальных адресов, помимо 127.0.0.1, множество. Весь диапазон 127.0.0.1/8 принадлежит интерфейсу loopback. К примеру, 127.123.45.67 — это тоже он, и все сервисы loopback тут тоже доступны.

Есть еще специальный локальный адрес — 0.0.0.0, а также адрес IPv6 — ::1.
Добавим сюда мультикаст-адреса, через которые можно общаться с некоторыми системами из окружения (примеры смотри в презентации по ссылке ниже). Еще можно использовать доменное имя localhost или, если хост состоит в домене, варьировать короткое или длинное имя хоста.

Игра с кодировкой.

IP-адрес или имя хоста можно попробовать закодировать. Черный список будет обойден, а адрес автоматически преобразуется в классический вид перед подключением. Интересно, что эта тема была обнаружена еще в 2000 году. И вот через пятнадцать лет она опять актуальна.

Читать также:  Как и зачем менять сервер DNS

Итак, мы все знаем про классическое десятеричное представление IP-адресов. Но оказывается, что есть и другие варианты. Причем варианты эти зависят от ОС, языка программирования и прочего. Несмотря на свою странность, работают они до сих пор (я проверял на Java и Python в Ubuntu и Windows).

Для эксперимента возьмем IP-адрес 188.226.235.38.

Для начала мы можем избавиться от точек между октетами (частями) IP-адреса.
Для этого нам нужно последовательно умножать каждое значение из октетов на 256 и суммировать с последующим.
188 256 = 48 128
48 128 + 226 = 48 354
48 354 256 = 12 378 624
12 378 624 + 235 = 12 378 859
12 378 859 * 256 = 3 168 987 904
3 168 987 904 + 38 = 3 168 987 942

3 168 987 942 — это то же самое, что и 188.226.235.38, только без точек. Называется такое представление IP-адреса Dword или Dotless.

Мы можем использовать шестнадцатеричные значения вместо десятичных. Для этого используем приставку «0x».
188 —> 0xbc
226 —> 0xe2
235 —> 0xeb
38 —> 0x26

Итого: 188.226.235.38 —> 0xbc.0xe2.0xeb.0x26. Кроме того, можно снова убрать точки и получить другое представление IP-адреса: 0xbce2eb26.

Использовать восьмеричное значение (octal IP). Тогда нужно добавлять 0 в начало.
188 —> 0274
226 —> 0342
235 —> 0353
38 —> 0046

Получается 188.226.235.38 —> 0274.0342.0353.0046.

Тут тоже есть трюк — можно добавлять произвольное количество нулей в начало. То есть IP-адрес 0000274.00000342.00353.000000046 такой же рабочий.

Также можно «перегружать» значения в IP-адресе. Если проще, то мы можем ставить значения более 255 (в десятичной системе). Значения более 255 будут уже больше одного байта, но лишние байты (слева) отсекаются.
188 — это 10111100, а 188 + 255 = 443, то есть 0000001 10111011.

Читать также:  Реализация Directory Traversal & Изъятие деликатной информации

При декодировании IP-адреса левый байт будет отброшен и опять останется только 188. На самом деле мы можем еще и еще добавлять 255 (правда, трех символов в октете уже быть не может). Добавляем в любой октет или во все сразу.

Аналогичный способ должен работать и для шестнадцатеричных значений. В теории к 0xbce2eb26 мы можем добавить любое значение слева. Например, числа 0x9A3F0800bce2eb26 и 9A3F0800 будут урезаны по тому же принципу. Но у меня данный метод нигде не заработал.

Все перечисленное можно смешивать в любой последовательности, а точки можно убирать частично. Например, 444.226.0000353.0×26 — это перегруженное десятичное, десятичное, восьмеричное и шестнадцатеричное значения. И это тоже IP-адрес.
Вариаций очень много, но не рассчитывай на уж очень изощренные варианты. Как я писал выше, есть зависимость от языка и от ОС — разные методы поддерживаются или не поддерживаются в разных местах.

Как видите, методов для обхода достаточно, но многое зависит от конкретной ситуации у жертвы и личной удачи. Большая часть примеров взята из презентации Server-side Browsing considered harmful Николаса Грегори c AppSec 2015 и проекта SafeCurl.

vgl7nxzcjxqUPD: копи-паст

Добавить комментарий