![]() |
![]() |
![]() |
![]() |
Как за неделю превратить Open redirect в RCE |
|
МЕНЮ Главная страница Поиск Регистрация на сайте Помощь проекту Архив новостей ТЕМЫ Новости ИИ Голосовой помощник Разработка ИИГородские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Искусственный интеллект Слежка за людьми Угроза ИИ ИИ теория Внедрение ИИКомпьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Нейронные сети начинающим Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Распознавание лиц Распознавание образов Распознавание речи Техническое зрение Чат-боты Авторизация |
2022-12-31 16:28 В этой статье я расскажу вам о том, как ровно год назад я связал в цепочку несколько проблем безопасности для достижения Удаленного выполнения кода (RCE) на нескольких серверах компании VK. Я постарался описать свои шаги в подробностях, так как мне самому, как постоянному читателю отчетов по баг-баунти, всегда хочется понять, как исследователь мыслит во время обнаружения необычных уязвимостей. Надеюсь, для вас эта статья будет интересна. Введение Не буду скрывать, я являюсь фанатом баг-баунти программы VK на HackerOne. Иногда холдинг приобретает новые компании, и программа пополняется новыми активами, что дает баг-хантерам неплохой шанс собрать "низко висящие фрукты" - уязвимости, которые могут быть найдены без существенных затрат времени и усилий. По своему опыту могу сказать, что получить доступ к чему-то, что до вас никто не пытался взломать, может быть очень выгодно. На площадке HackerOne есть возможность подписаться на интересующую программу и получать обновления о любых изменениях в правилах, чем я и воспользовался, чтобы быть одним из первых, кто начнет тестировать недавно добавленный сервис. В течение 2021 года я не очень активно хантил и особо не следил за обновлениями в избранной программе, именно по этой причине я пропустил уведомление о том, что платформа Seedr, которая помогает быстро распространять видео в интернете, сейчас уже приостановленная, была добавлена в скоуп. Моя первая встреча с Seedr состоялась в октябре 2021 года. В течение нескольких минут после начала тестирования я обнаружил несколько банальных XSS уязвимостей, но решил не сообщать о них, так как шанс получить дубликат был слишком высок. В настоящее время вы можете просмотреть несколько раскрытых отчетов других баг-хантеров и заметить, насколько нетипичные для современных приложений уязвимости они обнаружили в Seedr: Подумав, что мой поезд ушел, я решил не тратить много времени на Seedr и продолжил прокрастинировать. Находка, которая привлекла мое внимание Я вернулся к тестированию Seedr во время декабрьского отпуска в другой стране, где с собой у меня был лишь рюкзак и ноутбук. После некоторого времени пребывания в таких условиях у меня просыпается "баг-баунти голод" и появляется желание найти что-нибудь интересное. Для разогрева, я обычно возвращаюсь к уже знакомым сервисам и стараюсь взглянуть на них свежим взглядом. На этот раз я уделил больше внимания разведке Seedr, а именно: поиску и перечислению поддоменов, сканированию портов, перебору веб-директорий и так далее. К счастью, я нашел более заманчивые вещи: GitLab, Grafana, несколько хостов API, cron-файлы в веб-директории, трассировки стека и многое другое. Чем больше точек входа находишь - тем выше шанс найти что-то интересное. Хотя ни одна из находок не оказалась стоящей того, чтобы о ней сообщить, одна из них всё же привлекла мое внимание. В исходном HTML-коде страницы https://api-stage.seedr.ru/player я заметил следующий комментарий: ![]() Готов поспорить, что более опытный читатель уже захотел изменить GET-параметр config на свой хост для получения входящего HTTP-соединения, что я и сделал. Но после нескольких попыток не получил ни одного отстука и продолжил экспериментировать с другими параметрами. Когда я открыл ссылку ![]() После нескольких тестовых запросов я понял, что GET-параметры Предположив, что плеер скорее всего поддерживает не только YouTube, я изменил GET-параметр ![]() ![]() Итак, похоже, что в зависимости от значения GET-параметра GET-параметр Что ещё интересно, в случае с Vimeo, как вы могли заметить на предыдущем скриншоте, сервер делает запрос к http://vimeo.com/api/v2/video/VID.php. И оказывается, что при использовании расширения ![]() Я предположил, что после функции ![]()
Безопасная, пока ответ контролирует Vimeo. Возможные сценарии В тот момент у себя в голове я уже видел три возможных сценария атаки:
После нескольких часов различных модификаций GET-параметра Итак, первый сценарий не сработал, перейдем к следующему - контролируемому ответу на vimeo.com. Эндпоинт с контролируемым ответом должен отвечать следующим требованиям:
Ниже представлены некоторые из моих попыток найти требуемое поведение на vimeo.com:
Недостатки: код ответа HTTP 404 Not Found, не поддерживаются символы ![]()
Недостатки: код ответа HTTP 404 Not Found, не поддерживаются символы ![]()
Недостатки: ![]()
![]() Недостатки: Дата и имя в начале строки, требуется аутентификация. К сожалению, второй сценарий также не сработал, поэтому моей последней надеждой оставалось найти открытый редирект на 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:
![]()
![]()
![]() Из-за того, что возвращаемое значение из функции ![]() Как только я понял, что использовал почти весь потенциал этой SSRF, я переключился на эксплуатацию функции Небезопасная десериализация Вкратце объясню, что необходимо для успешной эксплуатации небезопасной десериализации в PHP:
Как видите, на тот момент выполнялось только одно требование из четырех. О серверном коде на хосте я знал слишком мало, поэтому единственный способ эксплуатации - это вслепую попробовать все известные цепочки гаджетов. Для этого я использовал инструмент PHPGGC, который по сути является набором полезных нагрузок для эксплуатации функции С помощью PHPGGC я предварительно сгенерировал все возможные нагрузки, разместил их на контролируемом сервере и начал перебор. Однако во всех случаях я получал одну и ту же ошибку: ![]()
В тот момент я уже смирился с тем, что уязвимый PHP-скрипт скорее всего примитивен и не подгружает никаких дополнительных классов, которые я бы мог использовать. Печально, но я хотя бы попытался. Так часто бывает, когда раскручиваешь крутую уязвимость, но сталкиваешься с чем-то, что полностью блокирует дальнейшее продвижение. После обобщения всех результатов я отправился на HackerOne и составил отчет под названием [player.seedr.ru] Semi-blind SSRF, не забыв пригласить Harsh Jaiswal в качестве соавтора за предоставленный открытый редирект на vimeo.com. На самом деле, на этом история могла бы и закончиться. Но внутри меня таилось чувство, которое не давало спать по ночам, намекая, что это ещё не конец и я должен попробовать что-нибудь ещё. Думаю, вам это чувство знакомо. Kohana Не помню, где именно, но несколько дней спустя мой взгляд случайно зацепился за какую-то информацию про уязвимость use-after-free в функции ![]()
Скорее всего, эта ошибка возникла потому, что мои сканеры отправили слишком много запросов, когда в предыдущие дни я искал скрытые веб-директории и файлы.
Благодаря Burp Suite Professional я быстро нашел первое упоминание о Kohana в истории прокси, открыл нужную ссылку и увидел подробную страницу ошибки. ![]() ![]() Здесь я сделаю небольшое отступление, чтобы рассказать вам немного о Seedr и о том, откуда взялся v2.nativeroll.tv. Однако стоит отметить, что вся информация, которую я буду предоставлять, является моими личными предположениями и может оказаться неточной. Seedr и Nativeroll - платформы для видеорекламы. У Seedr устаревший дизайн, поэтому я предположил, что он был создан задолго до Nativeroll. Обе платформы были куплены на тот момент ещё Mail.Ru Group, вероятно, каким-то образом объединены и размещены на HackerOne в одном скоупе. Таким образом, v2.nativeroll.tv/api/, api.seedr.ru, api-stage.seedr.ru, player.seedr.ru имели общую кодовую базу. Надеюсь, теперь стало немного понятнее. Хорошо, давайте вернемся к красивой странице с ошибкой. Environment, Included files, Loaded extensions - выглядит сочно. Вот что я увидел после нажатия на ссылку Included files: ![]() Почти 90 файлов, которые по сути были различными классами, подгруженные с помощью чего-то вроде ![]() Поскольку v2.nativeroll.ru и api.seed.ru имеют общую кодовую базу, я успешно вызвал Error exception на api.seedr.ru таким же способом ( Чтобы вызвать Error exception именно на api.seedr.ru/video (эндпоинт, который я атаковал), я взял ответ с http://vimeo.com/api/v2/video/123456.php и изменил тип значения атрибута description со строки на массив. ![]() ![]() Во время выполнения скрипта функция ![]() ![]() Как я и думал, там присутствовал скрипт автозагрузки Composer. Среди подгруженных файлов выделил несколько, которые могут быть полезны при десериализации:
Я знал, что в PHPGGC есть несколько цепочек гаджетов для Guzzle, Swift Mailer и Symfony. После того, как я сгенерировал и протестировал нагрузки на api-stage.seedr.ru, появились новые ошибки. Например, попытка с нагрузкой для Guzzle вернула ошибку ![]() Swift Mailer и Symfony не сработали вообще, и анализ кода Mustache и Sentry на Github также не принес никаких плодов, так что сторонние библиотеки меня не выручили. Пришло время погрузиться в Kohana. Поиск магических методов, таких как ![]() Но в этом репозитории есть каталог system, который на самом деле является отдельным репозиторием Kohana Core: ![]() ![]() Попробуем поискать магические методы уже в этом репозитории. Для ![]() Я бегло просмотрел результаты, и файл classes/Kohana/View.php и его функция render() сразу же привлекли мое внимание. Должен сказать, что в прошлом у меня был небольшой опыт бэкенд разработки. Я написал несколько проектов на Laravel и уже был знаком с паттерном MVC (Model-View-Controller). Для рендеринга шаблонов/представлений в Laravel используется движок Blade. Так как такие движки обычно загружают шаблоны, я предположил, что может быть я могу как-то передать в функцию свой собственный файл или свой собственный контент. Давайте внимательно рассмотрим функцию Функция Как сказано в комментарии, функция Функция Оказывается да! Мы можем передать его в качестве аргумента в функцию В тот момент у меня выполнялись все условия для успешной эксплуатация небезопасной десериализации:
Бинго! Всё вместе Через некоторое время я создал гаджет и цепочку для PHPGGC локально, которые позже были добавлены в основной репозиторий:
Затем я просто запустил PHPGGC и получил следующий сериализованный объект: ![]() Разместил нагрузку на контролируемом сервере, отправил запрос и ... ![]() По крайней мере, это было что-то новенькое. Но на что я надеялся? Ведь использовался метод ![]() Получается, мне как-то необходимо вывести объект ![]() Я снова обновил нагрузку на контролируемом сервере, отправил запрос и, наконец, получил ответ: ![]() Я получил содержимое файла /etc/passwd внутри метатега og:description. Круто, локальное чтение файлов намного лучше, чем полуслепая SSRF, но это всё ещё не RCE. Логи Уязвимость LFI настолько редкая находка в современных веб-приложениях, что мне пришлось вспоминать, где возможно разместить нагрузку, чтобы загрузить ее с помощью функции
![]() ![]()
![]()
![]() Как вы уже поняли, я перепробовал почти всё, но ничего не сработало. Пришло время сделать несколько шагов назад, а именно к ошибке, связанной с отсутствием места на устройстве: ![]() Из этой ошибки я смог извлечь путь к какому-то логу /application/logs/2021/12/20.php. После попытки открыть https://api.seedr.ru/application/logs/2021/12/20.php в браузере, я получил ошибку ![]() Похоже, что я не могу получить доступ к логам с расширением ![]() Да, я получил огромный лог-файл, после чего мой Burp Suite даже немного подвис. Должен отметить, что такой трюк не сработал на production хосте api.seedr.ru. Думаю, что разработчики Seedr специально что-то поменяли на stage хосте, чтобы упростить доступ к логам. Но, как обычно, это привело к проблеме безопасности. В очередной раз передо мной открылась новая дверь. Вы всё ещё помните, как я вызвал Error exception в первый раз ( ![]() После краткого анализа логов я "отравил" его такой записью: ![]() С помощью PHPGGC я сгенерировал новый сериализованный объект ![]() ![]() Поскольку лог за 20 декабря был испорчен моей неудачной нагрузкой, дальнейшее тестирование на этом хосте было бесполезно, поэтому я перешел к тестированию на локальном окружении. Многочасовая отладка и эксперименты с функцией Во время утреннего душа я вспомнил ещё одну потрясающую статью от Charlese Fol Laravel <= v8.4.2 debug mode: Remote code execution (CVE-2021-3129). В ней автор использует технику с особенностью множественного декодирования base64, которая игнорирует не base64 символы. Изначально я прочитал об этом в блоге тайваньского исследователя безопасности Orange Tsai. Моя идея заключалась в том, чтобы отравить лог PHP-нагрузкой закодированной в base64 несколько раз, а затем раскодировать его с помощью нескольких PHP-фильтров ![]() Из-за предсказуемого пути (/application/logs/2021/12/20.log) я скачал несколько логов за предыдущие дни и планировал отравить лог за 21 декабря в начале суток, пока он не стал слишком большим. Я добавил новую информацию в отчет на HackerOne и у меня в наличии оставался целый день до 21 декабря. Не теряя времени, я попытался проэксплуатировать уязвимость на api.seedr.ru, так как все последние тесты я проводил на api-stage.seedr.ru. Ещё раз с помощью PHPGGC сгенерировал объект
Нулевой байт Здесь я должен признаться, что когда генерировал сериализованный объект с помощью PHPGGC, я немного изменял его: ![]() Действительно ли строка ![]() Значение защищенного атрибута Как вы могли заметить по скриншотам, для хранения нагрузки я использовал сервис https://webhook.site/ - быстрое и простое решение для приема входящих HTTP-соединений и размещения нагрузки. К сожалению, в тот раз это сыграло со мной злую шутку. Дело в том, что для хранения защищенного значения в сериализованной строке PHP использует нулевые символы () вокруг символа "*". Вот почему ![]() Поскольку я просто копировал нагрузку на webhook.site, он не сохранял эти нулевые символы и передавал в функцию Последнее отправление В логах было множество различных типов записей, но лишь некоторые из них можно было использовать для хранения нагрузки, а большинство эндпоинтов вообще требовали аутентификацию. Уже при первом анализе логов я заметил следующий тип записи: ![]() Этот тип записи хорош тем, что он записывал нагрузку только один раз и не повторял ее, как в предыдущей попытке. Я также приметил возможную точку инъекции: заголовок user-agent. Но проблема заключалась в том, что я не знал, как именно сгенерировать такую запись в логе, к какому эндпоинту мне следует обратиться. Я грепнул лог со своим IP и обнаружил, что в сегодняшнем логе запись с моим IP уже присутствовала, что означало, что я уже точно обращался к нужному эндпоинту. К тому времени в моей истории Burp Proxy насчитывалось более 40000 записей, поэтому найти нужный эндпоинт оказалось не так-то просто. Сравнив время записи с моим IP и активностью, которой был занят в то время, я понял, что запись, вероятно, была сгенерирована во время сканирования с помощью dirsearch. Я запустил его повторно и через некоторое время эндпоинт, который генерировал такую запись, был найден - api-stage.seedr.ru/inc. На локальном окружении я спрятал новую нагрузку в тестовый лог, загрузил его через функцию На следующий день я "отравил" лог с помощью следующего запроса: ![]() Сгенерировал нагрузку, разместил на сервере, отправил запрос ... ![]() Да, я забыл поменять ![]() TL;DR ![]() Благодарность:
Полезные ссылки: https://twitter.com/rootxharsh https://infosecwriteups.com/vimeo-ssrf-with-code-execution-potential-68c774ba7c1e https://github.com/ambionics/phpggc https://www.ambionics.io/blog/laravel-debug-rce https://twitter.com/orange_8361 http://blog.orange.tw/2018/10/ P.S. Работа над уязвимостью велась в декабре 2021 года, но оказывается уже тогда существовал альтернативный способ эскалировать LFI до RCE, используя только обертки PHP. Таким образом "отравление" логов не потребовалось бы. Широко известно о новой технике стало не так давно, в октябре 2022 года. Почитать подробнее можно здесь: https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d https://www.synacktiv.com/publications/php-filters-chain-what-is-it-and-how-to-use-it.html Кстати, в узких кругах о гаджете с Kohana тоже судя по всему известно уже давно. В августе 2022 случайно обнаружил видео доклада Paul Axe от 2015 года: Источник: habr.com Комментарии: |
|