Подделка серверных запросов, эксплуатация Blind SSRF

By 28 января 2020 Blog

Есть такая штука, называется SSRF. Про нее написано немало, но все же, я расскажу тебе вкратце.

Допустим, ты заходишь на сайт, заполняешь профиль и доходишь до пункта “загрузить аватарку”. А у тебя выбор — загрузить файл или указать ссылку.

В данном случае, нас интересует второй пункт. Если мы укажем ссылку на ресурс, который содержит изображение, то веб-приложение:

1

Скачает ее

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

Проверит, что картинка — это картинка

По магическим хедерам, например.
3

Проверит размеры

Ведь они могут не подойти! Пусть будут дважды прокляты разработчики, которые в 20х годах просят загрузить строгие размеры типа 200x200 или 500x500.
4

Отобразит ее пользователю

Для резки нужной части. Многие делают это первым пунктом, отображая изображение пользователю и делают обрезку на части фронтенда.

Так вот. Если сайт не проверяет, откуда он пытается скачивать изображение — это уязвимость (как правило). Причем векторы атак, на такую маленькую фичу как загрузка изображений, настолько обширны, что не хватит и посиделки в баре, чтобы пройтись по всем вариантам.

Меня как-то спросили — “Что можно сделать, если можно засунуть одну лишь http(s) ссылку? Это же ничего не дает!”

Рассказываю.

Самый простой вариант — попытаться идентифицировать сервисы внутри. Если речь о картинке, можно попробовать обратиться к стандартным путям типа favicon.ico, логотипам или директории icons, предположив, что используется Apache. Отправляя запросы, мы можем перебирать популярные локальные адреса, а также поддомены сайта, которые работают во внутренней инфраструктуре. Это всякие bamboo, jira, gitlab и прочие любимые всеми компаниями штуки.

Нафига это нужно? А потому что знание — сила. Ведь даже вслепую можно использовать различные уязвимости и эксплойты. Зная вендора или версию веб-сервера или используемого сервиса, ты сужаешь круг атак, который можешь применить. Может даже и не сейчас, а в будущем, знание технической информации о внутренней инфраструктуре поможет тебе в эксплуатации других уязвимостей.

Ну ладно, разрешать вводить IP адреса нам запретили. Предположим, что у нас есть какой-то важный ресурс внутри инфраструктуры и IP адрес его 192.168.1.1

В первую очередь мы мысленно заведем домен, которому присвоим этот IP. Пусть это будет my-test-site.com. В реальной жизни следует завести поддомены, адрес которых будет направлять на нужные нам IP, но об этом чуть позже.

Перебор пароля

Давайте помечтаем, что у нас внутри находится роутер. Директория /admin/ находится под Basic auth. Изменяя ссылку, мы можем подбирать логины и пароли к роутеру. Но тут совсем просто, мы просто формируем ссылку вида:

http://login:[email protected]/admin/

Таким образом, значение до двоеточия будет логином, после него — паролем. А через знак @ будет домен, куда эти данные будут направлены. Учти, что не работает с всякими вконтактиками, где нужно заполнять форму. Поэтому нужна именно Basic аутентификация — всплывающее окошко с ответом 401 от сервера.

Если бы нам повезло, и сайт отдавал ХОТЯ БЫ код ответа (200, 401, 503), то было бы намного легче. Тогда мы можем явно наблюдать за процессом и видеть свою победу:

http://admin:[email protected]/admin/ - 401
http://admin:[email protected]/admin/ - 401
http://admin:[email protected]/admin/ - 200

Отправив десяток — другой таких запросов, можно попытаться подобрать пароль к этому роутеру. А потом обратиться на сценарий сохранения собственного DNS или даже /reboot.cgi

А если ответа нет и он всегда один и тот же?

Тут нам помогут тайминги.

Тайминги

Все требует времени. Как я отнимаю у тебя время на чтение статьи, так и сервисы отнимают время на ответ.
Особенность в том, что мы можем пробовать обращаться к внутренним ресурсам и измерять тайминги для ответа на вопрос — есть там сервис или нет?
Отправив множество запросов, можно вслепую перебрать внутренние сервисы, порты и даже директории и файлы, опираясь на аномалию ответов.

1302 ms - http://test.company.com
1307 ms - http://db.company.com
5483 ms - http://jira.company.com
1410 ms - http://docs.company.com
1366 ms - http://kafka.company.com

Поддомен jira дольше всех отвечал, скорее всего он есть внутри, а разница заметна из-за того, что веб-сервер попытался загрузить страницу, а потом понял, что это не изображение. Причем нам важен не факт “Кто дольше всех отвечал?”, а именно “Кто отвечал не как все?”. Потому что тайминг может быть как задержка — например, если ты нашел большой файл или сценарий, который долго выполняется. Либо наоборот, быстрый ответ.

1302 ms - http://test.company.com
1307 ms - http://db.company.com
423 ms - http://jira.company.com
1410 ms - http://docs.company.com
1366 ms - http://kafka.company.com

В этом случае ответ говорит о том, что либо это 401, либо редирект который не обрабатывает парсер изображений. А может и то, что сайт доступен, но проверив первые байты или content-type, наше веб-приложение отвергло его до того, как скачать страницу полностью. Другие сайты в этом примере не дождались соединения (или не отрезолвился хостнейм).

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

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

При первом запросе домена my-test-site.com отдать внешний IP, например 123.123.123.123
А как только он пройдет валидацию, начать отдавать на тот же домен внутренний IP.

На этот случай Emil Lerner сделал классный сервис — 1u.ms!

Домен 1u.ms отвечает теми IP адресами, которые ты указал.

Формат домена должен быть следующий:

make-%IP%-rebind-%IP-rr.1u.ms

Например

make-123.123.123.123-rebind-127.0.0.1-rr.1u.ms

на первый запрос ответит адресом 123.123.123.123, а на второй запрос — уже на 127.0.0.1 (если они будут друг за другом, в течение 5 секунд).

Кстати, IP адрес можно написать без точек, на случай, если нужен поддомен:

make-123-123-123-123-rebind-127-0-0-1-rr.1u.ms

До ключевого слова make и после rr можно писать произвольные слова, дабы не давать использовать кэшированные данные:

bo0om-make-123-123-123-123-rebind-127-0-0.1-rr-test.1u.ms

А для просмотра лога — аналог tail -f:

curl http://1u.ms:8080/log

(или эта же ссылка в браузере)

Сканирование портов используя DNS

На самом деле управляя DNS записями можно попытаться сканировать порты. А поможет нам в этом небольшой трюк.

Предположим, у нас есть домен my-test-site.com.

Обычно, у него содержится как минимум одна A запись, чтобы ресурс открывался.
Допустим, IP нашего сайта 172.217.20.46 (взял у google.com). Но мы можем указать несколько таких записей! Представим, что мы их создали и наши DNS записи выглядят подобным образом:

my-test-site.com 172.217.20.46
my-test-site.com 192.168.1.1

Будет ли открываться наш ресурс? Да, будет. А все потому, что первая запись идет первой.

Теперь изменим DNS записи вот так:

my-test-site.com 192.168.1.1
my-test-site.com 172.217.20.46

Будет ли открываться наш ресурс? Снова будет 🙂

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

curl "my-test-site.com" -v
* Trying 192.168.1.1...
* TCP_NODELAY set
* Immediate connect fail for 192.168.1.1: Host is down
* Trying 172.217.20.46...
* TCP_NODELAY set
* Connected to my-test-site.com (172.217.20.46) port 80 (#0)
> GET / HTTP/1.1
> Host: my-test-site.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Type: text/html; charset=UTF-8
< Referrer-Policy: no-referrer
< Content-Length: 1561
< Date: Tue, 21 Jan 2020 16:35:08 GMT

Используя эту особенность, можно узнавать какие порты открыты, а какие — нет. Ведь многие (но не все) библиотеки будут пытаться прийти сначала на первый ресурс, который указан первым в DNS записях, а потом на второй.
Вот мы указали ссылку на наш ресурс в загрузке аватарки.

Указывая наш домен, меняя порт, методом исключения пытаемся установить, какой из портов доступен.

http://my-test-site.com:22 - пришел запрос на 172.217.20.46:22
http://my-test-site.com:80 - пришел запрос на 172.217.20.46:80
http://my-test-site.com:8080 - запрос не пришел
http://my-test-site.com:9200 - пришел запрос на 172.217.20.46:9200
http://my-test-site.com:3306 - запрос не пришел

Так как первой записью у нас указан 192.168.1.1, делаем вывод, что этот адрес ответил библиотеке на 192.168.1.1 (порты 8080 и 3306), пусть даже и некорректно, но ответил. Значит эти порты открыты и сервисы на них есть. В таком случае она не будет переключаться на второй адрес.

Сервис 1u.ms тут также может помочь, в этом случае у нас будет следующий конфиг:

make-%IP%-and-%IP%rr.1u.ms

типа

make-192.168.1.1-and-172.217.20.46rr.1u.ms

Он вернет две записи, все остальные фичи как из предыдущего пункта.

Кстати, такой подход можно использовать для более быстрого DNS Rebinding’а, заставляя браузер переключаться с “зависшего” сервера на рабочий. Думаю, это будет быстрее, чем ждать минуту для обновления DNS-кэша. Однако с Chrome, например, такой фокус не пройдет, так как он берет случайный IP.

Еще одна загвоздка в том, что DNS сервер, с помощью которого веб-приложение определяет адреса доменов, может иметь встроенный round robin — менять порядок записей, тем самым равномерно распределять нагрузку на все сервера. Например, 8.8.8.8 от этой фичи отказался, а у 1.1.1.1 она присутствует.

Перенаправления

Пробуй в редиректы! Ну, во-первых, редиректами можно уменьшить количество запросов к веб-приложению, например при сканировании портов с использованием DNS. Если ответ дошел до тебя — перенаправить на другой порт или домен. Если нет, возможно он споткнулся на открытый порт.

Но круче всего, конечно же, попытаться сменить протокол с помощью перенаправления. На моей практике были случаи, когда срабатывал редирект на file:///etc/passwd и показывал содержимое файла. В варианте с Blind SSRF можно попробовать сменить протокол на gopher (пока он еще существует), и уже можно отправлять письма с помощью команд к SMTP и прочую магию.

Отказ в обслуживании

Остановится ли загрузчик, если дать ему на вход файл размером в 10 ГБ? Или изображение размером 225,000 на 225,000 пикселей, занимающее 141.4 ГБ в оперативной памяти. Это может сказаться на работоспособности сайта. Правда, упавший сервер нам не принесет никакого фана, так что просто держи это в голове.

И еще куча всего

Наверное, это все, что я могу сейчас назвать. Это не учитывая уязвимостей связанных с аплоадом (куда загружается, как сохраняется, что проверяется при этом) и 3rd-party штук (вспоминаем imagetragick и gifoeb). Если у тебя есть еще какие-то идеи — оставь комментарий.

Не могу не дать SSRF библию, которая описывает большинство кейсов по этой атаке. А также страница с SSRF на всеми любимом репозитории PayloadsAllTheThings, который активно поддерживается комьюнити. Кстати, многие атаки применимы как к server-side (на сервере), так и к client-side (в браузере). Атакующий — атакуй, будь изобретателен. Защищающийся — защищайся, будь хитрее чем атакующие.

Join the discussion 2 комментария

Leave a Reply