Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этой статье я расскажу о том, как я связал в цепочку несколько проблем безопасности с целью удаленного выполнения кода (RCE) на серверах компании VK. Я описал свои шаги в подробностях, чтобы показать, как исследователь мыслит во время обнаружения необычных уязвимостей.
Pentest Award
В августе 2023 года прошла церемония награждения Pentest Award — премии для специалистов по тестированию на проникновение, которую учредила компания Awillix. Мы публикуем лучшие работы из каждой номинации. Эта статья — победитель в номинации «Пробив». Читай также райтапы, занявшие второе, третье и четвертое места.
Не буду скрывать, я — фанат программы баг‑баунти VK на HackerOne. Иногда холдинг приобретает новые компании, и программа расширяется, что дает баг‑хантерам неплохой шанс собрать «низко висящие фрукты» — уязвимости, которые могут быть найдены без существенных затрат времени и усилий.
По своему опыту могу сказать, что получить доступ к чему‑то, что до тебя никто не пытался взломать, очень выгодно. На площадке HackerOne есть возможность подписаться на интересующую тебя программу и получать обновления о любых изменениях в правилах. Этим я и воспользовался, чтобы быть одним из первых, кто начнет тестировать недавно добавленный сервис.
В течение 2021 года я не очень активно хантил и не следил за обновлениями в избранной программе. Именно по этой причине я пропустил уведомление о том, что платформа Seedr, которая помогает быстро распространять видео в интернете (сейчас уже не действующая), была добавлена в скоуп.
Моя первая встреча с Seedr состоялась в октябре 2021 года. Я начал тестировать и сразу же обнаружил несколько банальных XSS-уязвимостей, но решил не сообщать о них, так как шанс получить дубликат был слишком высок.
В настоящее время ты можешь просмотреть раскрытые отчеты других баг‑хантеров и заметить, насколько нетипичные для современных приложений уязвимости они нашли в Seedr:
Подумав, что мой поезд ушел, я решил не тратить силы на этот проект и продолжил прокрастинировать.
Я вернулся к тестированию Seedr во время декабрьского отпуска в другой стране, где с собой у меня был лишь рюкзак и ноутбук. После некоторого времени пребывания в таких условиях у меня просыпается «баг‑баунти‑голод» и появляется желание найти что‑нибудь интересное. Для разогрева я обычно возвращаюсь к уже знакомым сервисам и стараюсь взглянуть на них свежим взглядом.
На этот раз я уделил больше внимания разведке Seedr, а именно поиску и перечислению поддоменов, сканированию портов, перебору веб‑директорий и так далее. К счастью, я нашел более заманчивые вещи: GitLab, Grafana, несколько хостов API, cron-файлы в веб‑директории, трассировки стека и многое другое. Чем больше точек входа находишь, тем выше шанс отыскать что‑то интересное. Хотя ни одна из находок не оказалась стоящей того, чтобы о ней сообщить, кое‑что все же привлекло мое внимание.
В исходном HTML-коде страницы https://api-stage.seedr.ru/player
я заметил следующий комментарий:
https://player.seedr.ru/video?vid=cpapXGq50UY&post_id=57b6ceef64225d5b0f8b456c&config=https%3A%2F%2Fseedr.com%2Fconfig%2F57975d1b64225d607e8b456e.json&hosting=youtube
Готов поспорить, что более опытный читатель уже захотел изменить GET-параметр config
на свой хост для получения входящего HTTP-соединения, что я и сделал. Но после нескольких попыток не получил ни одного отстука и продолжил экспериментировать с другими параметрами.
Когда я открыл в браузере ссылку https://player.seedr.ru/video?vid=cpapXGq50UY&post_id= 57b6ceef64225d5b0f8b456c&config=https%3A%2F%2Fseedr.com%2Fconfig%2F57975d1b64225d607e8b456e.json&hosting=youtube
, я заметил, что метатеги заполнены по разметке Open Graph и содержат информацию о видео: название, описание, превью и так далее.
После нескольких тестовых запросов я понял, что GET-параметры post_id
и config
не оказывают существенного влияния на ответ, поэтому упростил URL до https://player.seedr.ru/video?vid=cpapXGq50UY&hosting=youtube
.
Предположив, что плеер, скорее всего, поддерживает не только YouTube, я изменил GET-параметр hosting
на coub
и vimeo
.
Итак, похоже, в зависимости от значения GET-параметра hosting
сервер с помощью PHP-функции file_get_contents()
выполняет HTTP-запрос к YouTube, Vimeo или Coub API, загружает метаданные о видео (GET-параметр vid
), обрабатывает их и возвращает HTML-страницу плеера с видео и заполненными по разметке Open Graph метатегами.
GET-параметр vid
является точкой инъекции, так как он позволяет контролировать последнюю часть пути в функции file_get_contents()
с помощью символов обхода пути (/../
) и других полезных символов (?, #, @ и так далее).
Что еще интересно, в случае с Vimeo сервер делает запрос к http://vimeo.com/api/v2/video/VID.php
. И оказывается, что при использовании расширения .php
в пути Vimeo возвращает не JSON, а сериализованные данные!
Я предположил, что после функции file_get_contents()
сервер десериализует ответ от Vimeo с помощью функции unserialize()
.
Ого, неужели у нас здесь небезопасная десериализация? Безопасная, пока ответ контролирует Vimeo.
В тот момент у себя в голове я уже видел три возможных сценария атаки:
file_get_contents()
с целью добиться слепой SSRF, то есть выполнить HTTP-запрос на подконтрольный мне ресурс и в теории добиться небезопасной десериализации.
vimeo.com
и добиться небезопасной десериализации.
vimeo.com
→ SSRF → небезопасная десериализация.
После нескольких часов различных модификаций GET-параметра vid
и локального фаззинга функции file_get_contents()
я не нашел ничего полезного и параллельно решил поделиться всей имеющейся информацией об этой находке с несколькими надежными товарищами.
Итак, первый сценарий не сработал, поэтому я перешел к следующему — контролируемому ответу на vimeo.com
.
Эндпоинт с контролируемым ответом должен отвечать следующим требованиям:
Ниже представлены некоторые из моих попыток найти требуемое поведение на vimeo.com
.
Первая: injection
is not a valid method.
Недостатки: код ответа HTTP 404 Not Found, не поддерживаются символы {}
, ""
.
Вторая: injection
is not a valid format.
Недостатки: код ответа HTTP 404 Not Found, не поддерживаются символы {}
, ""
.
Третья: JavaScript callback.
Недостатки: /**/
в начале строки, не поддерживаются символы {}
, ""
.
Четвертая: экспорт чата прямой трансляции.
Недостатки: дата и имя в начале строки, требуется аутентификация.
К сожалению, второй сценарий также не сработал, поэтому моей последней надеждой оставалось найти открытый редирект на vimeo.com
. Ранее я уже встречал опубликованный отчет на HackerOne от 2015 года с открытым редиректом на vimeo.com
, поэтому предположил, что есть небольшой шанс найти еще один. На самом деле я одновременно искал открытый редирект еще во время проверки второго сценария, но снова ничего не нашел.
Все это время, пока я раскручивал уязвимость, я думал о статье Harsh Jaiswal Vimeo SSRF with code execution potential. Я отчетливо помнил, что для успешной эксплуатации использовалось несколько открытых редиректов на vimeo.com
. Уязвимость была найдена еще в 2019 году, поэтому я ожидал, что описываемые в статье открытые редиректы уже исправлены. Но поскольку, вероятно, это был мой единственный шанс, я начал копать в этом направлении.
Из‑за того, что информация на скриншотах была недостаточно скрыта, удалось предположить уязвимый эндпоинт по используемым GET-параметрам. Учитывая это, я немного погуглил и почитал документацию Vimeo API и смог определить, какой именно эндпоинт использовал Harsh в своей цепочке. В любом случае оставалось неясным, какие значения GET-параметров я должен передать.
Я редко прошу кого‑то о помощи, не считая нескольких друзей, но, поскольку я был в тупике, Harsh был моей последней надеждой.
После того как я написал ему и предоставил всю имеющуюся информацию, он поделился со мной рабочей ссылкой с открытым редиректом, которая оказалась такой же, как я и подозревал, но с верными значениями GET-параметров. По этой ссылке я понял, что это не баг на vimeo.com
, а фича (действительно, это не шутка).
Итак, теперь у меня есть работающий открытый редирект на vimeo.com
, осталось только его применить.
Отлично, я наконец‑то словил HTTP-запрос на свой хост. Прежде чем перейти к десериализации, я решил немного поиграть с SSRF. Результаты смотри на скриншотах.
https://127.0.0.1
https://127.0.0.1:22
http://127.0.0.1:25
Из‑за того что возвращаемое значение из функции file_get_contents()
передается сразу в функцию unserialize()
, у меня не получилась полная SSRF, чтобы читать успешные ответы от внутренних сервисов. Но по крайней мере, у меня уже была полуслепая SSRF с возможностью выполнять сканирование портов.
Как только я понял, что использовал почти весь потенциал этой SSRF, я переключился на эксплуатацию функции unserialize()
.
Вкратце объясню, что необходимо для успешной эксплуатации небезопасной десериализации в PHP:
__wakeup()
, __destroy()
, __toString()
и так далее);
Как видишь, на тот момент выполнялось только одно требование из четырех. О серверном коде на хосте я знал слишком мало, поэтому единственный способ эксплуатации — это вслепую попробовать все известные цепочки гаджетов. Для этого я использовал инструмент PHPGGC, который по сути является набором полезных нагрузок для эксплуатации функции unserialize()
вместе с инструментом для их генерации. В то время он содержал почти 90 доступных нагрузок. Большая часть из них предназначена для различных CMS и фреймворков, таких как WordPress, ThinkPHP, TYPO3, Magento, Laravel, которые в моем случае были совершенно бесполезны. Поэтому я сделал ставку на такие широко используемые библиотеки, как Doctrine, Guzzle, Monolog и Swift Mailer.
Источник: xakep.ru