В этой статье я расскажу вам о том, как ровно год назад я связал в цепочку несколько проблем безопасности для достижения Удаленного выполнения кода (VK. Я постарался описать свои шаги в подробностях, так как мне самому, как постоянному читателю отчетов по баг-баунти, всегда хочется понять, как исследователь мыслит во время обнаружения необычных уязвимостей. Надеюсь, для вас эта статья будет интересна.
Введение
Не буду скрывать, я являюсь фанатом баг-баунти программы VK на HackerOne. Иногда холдинг приобретает новые компании, и программа пополняется новыми активами, что дает баг-хантерам неплохой шанс собрать «низко висящие фрукты» — уязвимости, которые могут быть найдены без существенных затрат времени и усилий.
По своему опыту могу сказать, что получить доступ к чему-то, что до вас никто не пытался взломать, может быть очень выгодно. На площадке HackerOne есть возможность подписаться на интересующую программу и получать обновления о любых изменениях в правилах, чем я и воспользовался, чтобы быть одним из первых, кто начнет тестировать недавно добавленный сервис.
В течение 2021 года я не очень активно хантил и особо не следил за обновлениями в избранной программе, именно по этой причине я пропустил уведомление о том, что платформа Seedr, которая помогает быстро распространять видео в интернете, сейчас уже приостановленная, была добавлена в скоуп.
Моя первая встреча с Seedr состоялась в октябре 2021 года. В течение нескольких минут после начала тестирования я обнаружил несколько банальных RCE в .api/nr/report/{id}/download
Подумав, что мой поезд ушел, я решил не тратить много времени на 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()
с целью добиться слепой , необходимые для хранения сериализованных объектов. -
injection is not a valid method.
-
injection is not a valid format.
-
JavaScript callback.
-
Экспорт чата прямой трансляции:
-
https://127.0.0.1
-
https://127.0.0.1:22
-
http://127.0.0.1:25
-
Контролируемые входные данные; -
Класс с магическим методом (
__wakeup()
,__destroy()
,__toString()
и т.д.); -
В магическом методе определена полезная функциональность, которой можно злоупотребить (манипуляция с файловой системой, выполнение запросов к базе данных и т.п.);
-
Класс загружен.
-
Guzzle (/var/www/sentry/vendor/guzzlehttp/…)
-
Swift Mailer (MODPATH/email/vendor/swiftmailer/…)
-
Symfony (/var/www/sentry/vendor/symfony/…)
-
Mustache (MODPATH/kostache/vendor/mustache/…)
-
Sentry (/var/www/sentry/vendor/sentry/…)
-
…
-
Я контролировал входные данные;
-
У меня был волшебный метод
__toString()
классаView
с полезной функциейinclude()
. -
Класс
View
был загружен. -
загрузка файлов (в моем случае в приложении не было такой функциональности);
-
логи (apache, nginx, mail, ssh, …);
-
/proc/*/fd, /proc/self/environ;
-
файл PHP-сессии;
-
@rootxharsh за то, что поделился открытым перенаправлением;
-
@act1on3 и моего личного эксперта по PHP за то, что они были моими резиновыми уточками.
Ниже представлены некоторые из моих попыток найти требуемое поведение на 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:
Из-за того, что возвращаемое значение из функции file_get_contents()
передается сразу в функцию unserialize()
, у меня не получилась полная SSRF, чтобы читать успешные ответы от внутренних сервисов. Но, по крайней мере, у меня уже была полуслепая SSRF с возможностью выполнять сканирование портов:
Как только я понял, что использовал почти весь потенциал этой SSRF, я переключился на эксплуатацию функции unserialize()
.
Небезопасная десериализация
Вкратце объясню, что необходимо для успешной эксплуатации небезопасной десериализации в PHP:
Как видите, на тот момент выполнялось только одно требование из четырех. О серверном коде на хосте я знал слишком мало, поэтому единственный способ эксплуатации — это вслепую попробовать все известные цепочки гаджетов. Для этого я использовал инструмент PHPGGC, который по сути является набором полезных нагрузок для эксплуатации функции unserialize()
вместе с инструментом для их генерации. В то время он содержал почти 90 доступных нагрузок. Большая часть из них предназначена для различных CMS и фреймворков, таких как WordPress, ThinkPHP, Typo3, Magento, Laraver и т.д., которые в моем случае были совершенно бесполезны. Поэтому я сделал ставку на такие широко используемые библиотеки, как Doctrine, Guzzle, Monolog и Swift Mailer.
С помощью PHPGGC я предварительно сгенерировал все возможные нагрузки, разместил их на контролируемом сервере и начал перебор. Однако во всех случаях я получал одну и ту же ошибку:
The error occurs because inside serialized string there is a reference to a class that hasn’t been included yet — so the PHP autoloading mechanism is triggered to load that class, and this fails for some reason. © Sven
В тот момент я уже смирился с тем, что уязвимый PHP-скрипт скорее всего примитивен и не загружает никаких дополнительных классов, которые я бы мог использовать. Печально, но я хотя бы попытался. Так часто бывает, когда раскручиваешь крутую уязвимость, но сталкиваешься с чем-то, что полностью блокирует дальнейшее продвижение.
После обобщения всех результатов я отправился на HackerOne и составил отчет под названием [player.seedr.ru] Semi-blind SSRF, не забыв пригласить Harsh Jaiswal в качестве соавтора за предоставленный открытый редирект на vimeo.com.
На самом деле, на этом история могла бы и закончиться. Но внутри меня таилось чувство, которое не давало спать по ночам, намекая, что это ещё не конец и я должен попробовать что-нибудь ещё. Думаю, вам это чувство знакомо.
Kohana
Не помню, где именно, но несколько дней спустя мой взгляд случайно зацепился за какую-то информацию про уязвимость use-after-free в функции unserialize()
. Версия PHP на player.seedr.ru оказалось устаревшей, и я сразу начал «исследовать» эту тему. В ходе этих «исследований» я ознакомился с отчетами Taoguang Chen, который сообщил команде PHP несколько десятков проблем с функцией unserialize()
. Хотя уязвимости, связанные с памятью, все ещё тёмный лес для меня, я все же постарался сгенерировать несколько нагрузок. После продолжительных тестов локально, я вернулся на player.seedr.ru, разместил нагрузку на контролируемом сервере, отправил запрос, и …
«Серьезно? На устройстве не осталось места? Я только начал. Но, подождите, это не похоже на стандартную ошибку о закончившемся месте на устройстве».
Скорее всего, эта ошибка возникла потому, что мои сканеры отправили слишком много запросов, когда в предыдущие дни я искал скрытые веб-директории и файлы.
ErrorException [ 2 ]: file_put_contents(/var/www/seedr.backend.v2/application/logs/2021/12/20.php): failed to open stream: No space left on device ~ SYSPATH/classes/kohana/log/file.php [ 81 ]
«Кастомный класс для ведения журнала? Видимо, что этот «примитивный» PHP скрипт все же загружает класс логирования, интересно. Kohana? Я уже встречал это слово во время тестирования Seedr. Но где?»
Благодаря 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 файлов, которые по сути были различными классами, подгруженные с помощью чего-то вроде autoload.php
. Является ли Kohana чем-то вроде CMS или фреймворка? Да, это так. После небольшого поиска я нашел на GitHub репозиторий, который выглядит заброшенным:
Поскольку v2.nativeroll.ru и api.seed.ru имеют общую кодовую базу, я успешно вызвал Error exception на api.seedr.ru таким же способом (https://api.seedr.ru/<svg>
) и получил тот же результат.
Чтобы вызвать Error exception именно на api.seedr.ru/video (эндпоинт, который я атаковал), я взял ответ с http://vimeo.com/api/v2/video/123456.php и изменил тип значения атрибута description со строки на массив.
Во время выполнения скрипта функция htmlspecialchars()
ожидала строку, но получила массив, что вызвало Error exception с частичным раскрытием PHP-шаблона и трассировкой стека:
Как я и думал, там присутствовал скрипт автозагрузки Composer. Среди подгруженных файлов выделил несколько, которые могут быть полезны при десериализации:
Я знал, что в PHPGGC есть несколько цепочек гаджетов для Guzzle, Swift Mailer и Symfony. После того, как я сгенерировал и протестировал нагрузки на api-stage.seedr.ru, появились новые ошибки. Например, попытка с нагрузкой для Guzzle вернула ошибку FnStream never should be unserialized
. Это указывало на то, что скрипт использовал уже исправленную версию:
Swift Mailer и Symfony не сработали вообще, и анализ кода Mustache и Sentry на Github также не принес никаких плодов, так что сторонние библиотеки меня не выручили. Пришло время погрузиться в Kohana.
Поиск магических методов, таких как __wakeup()
, __destruct()
, __toString()
, в репозитории Kohana оказался безрезультативным:
Но в этом репозитории есть каталог system, который на самом деле является отдельным репозиторием Kohana Core:
Попробуем поискать магические методы уже в этом репозитории. Для __destruct()
, __wakeup()
результатов почти нет, но результаты для __toString()
обнадеживают:
Я бегло просмотрел результаты, и файл classes/Kohana/View.php и его функция render() сразу же привлекли мое внимание.
Должен сказать, что в прошлом у меня был небольшой опыт бэкенд разработки. Я написал несколько проектов на Laravel и уже был знаком с паттерном MVC (Model-View-Controller). Для рендеринга шаблонов/представлений в Laravel используется движок Blade. Так как такие движки обычно загружают шаблоны, я предположил, что может быть я могу как-то передать в функцию свой собственный файл или свой собственный контент.
Давайте внимательно рассмотрим функцию render()
:
public function render($file = NULL) { if ($file !== NULL) { $this->set_filename($file); } if (empty($this->_file)) { throw new View_Exception('You must set the file to use within your view before rendering'); } // Combine local and global data and capture the output return View::capture($this->_file, $this->_data); }
Функция render()
принимает один аргумент под названием $file
, а затем вызывает функцию capture()
.
protected static function capture($kohana_view_filename, array $kohana_view_data) { // Import the view variables to local namespace extract($kohana_view_data, EXTR_SKIP); if (View::$_global_data) { // Import the global view variables to local namespace extract(View::$_global_data, EXTR_SKIP | EXTR_REFS); } // Capture the view output ob_start(); try { // Load the view within the current scope include $kohana_view_filename; } catch (Exception $e) { // Delete the output buffer ob_end_clean(); // Re-throw the exception throw $e; } // Get the captured output and close the buffer return ob_get_clean(); }
Как сказано в комментарии, функция capture()
объединяет локальные и глобальные переменные и фиксирует вывод.
Функция capture()
принимает два аргумента: $kohana_view_filename
и $kohana_view_data
. Некоторые из вас, вероятно, уже заметили функцию, которой потенциально можно злоупотребить при десериализации:
try { // Load the view within the current scope include $kohana_view_filename; }
В тот момент у меня выполнялись все условия для успешной эксплуатация небезопасной десериализации: Бинго! Через некоторое время я создал гаджет и цепочку для 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 сгенерировал объект «Упс, неужели уязвим только stage хост?». Здесь я должен признаться, что когда генерировал сериализованный объект с помощью PHPGGC, я немного изменял его: Действительно ли строка Значение защищенного атрибута Как вы могли заметить по скриншотам, для хранения нагрузки я использовал сервис https://webhook.site/ — быстрое и простое решение для приема входящих HTTP-соединений и размещения нагрузки. К сожалению, в тот раз это сыграло со мной злую шутку. Дело в том, что для хранения защищенного значения в сериализованной строке PHP использует нулевые символы (\0) вокруг символа «*». Вот почему Поскольку я просто копировал нагрузку на webhook.site, он не сохранял эти нулевые символы и передавал в функцию В логах было множество различных типов записей, но лишь некоторые из них можно было использовать для хранения нагрузки, а большинство эндпоинтов вообще требовали аутентификацию. Уже при первом анализе логов я заметил следующий тип записи: Этот тип записи хорош тем, что он записывал нагрузку только один раз и не повторял ее, как в предыдущей попытке. Я также приметил возможную точку инъекции: заголовок user-agent. Но проблема заключалась в том, что я не знал, как именно сгенерировать такую запись в логе, к какому эндпоинту мне следует обратиться. Я грепнул лог со своим IP и обнаружил, что в сегодняшнем логе запись с моим IP уже присутствовала, что означало, что я уже точно обращался к нужному эндпоинту. К тому времени в моей истории Burp Proxy насчитывалось более 40000 записей, поэтому найти нужный эндпоинт оказалось не так-то просто. Сравнив время записи с моим IP и активностью, которой был занят в то время, я понял, что запись, вероятно, была сгенерирована во время сканирования с помощью dirsearch. Я запустил его повторно и через некоторое время эндпоинт, который генерировал такую запись, был найден — api-stage.seedr.ru/inc. На локальном окружении я спрятал новую нагрузку в тестовый лог, загрузил его через функцию На следующий день я «отравил» лог с помощью следующего запроса: Сгенерировал нагрузку, разместил на сервере, отправил запрос … Да, я забыл поменять 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/ Работа над уязвимостью велась в декабре 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 года:include()
! Это уже попахивает public function __construct($file = NULL, array $data = NULL) { if ($file !== NULL) { $this->set_filename($file); } if ($data !== NULL) { // Add the values to the current data $this->_data = $data + $this->_data; } }
Всё вместе
<?php namespace GadgetChain\Kohana; class FR1 extends \PHPGGC\GadgetChain\FileRead { public static $version = '3.*'; public static $vector = '__toString'; public static $author = 'byq'; public static $information = 'include()'; public function generate(array $parameters) { return new \View($parameters['remote_path']); } }
<?php class View { protected $_file; public function __construct($_file) { $this->_file = $_file; } }
__toString()
, а не методы __wakeup()
или __destruct()
, которые срабатывают в момент создания и уничтожения объекта соответственно. В документации PHP сказано:View
. На самом деле несложно было понять, что я должен передать свой объект View
в качестве значения атрибута title или description — трюк, который я проделал ранее с массивом, чтобы вызвать Error exception. Вот как выглядела моя нагрузка:Логи
include()
и получить RCE. Наиболее распространенными техниками являются:
No direct script access
. Почти в каждом PHP-файле фреймворка Kohana есть такая строчка в начале:.php
непосредственно из браузера. К моему удивлению, попробовав открыть на stage хосте http://api-stage.seedr.ru/application/logs/2021/12/20.php, я получил код овета HTTP 404. Не знаю, что меня подтолкнуло, но я изменил расширение .php
на .log
, и …https://api.seedr.ru/<svg>
)? Вот запись об этом в логе:View
с файлом /var/www/t1.seedr.backend/application/logs/2021/12/20.log, разместил его на контролируемом сервере, отправил запрос и получил следующую ошибку:Видимо, из-за того, что файл журнала был слишком большим (>200000 строк), какая-то функция ломалась на одном из символов «?», выбрасывала исключение и останавливала выполнение скрипта. На самом деле я просто опечатался, предлагаю вам найти ошибку в моей нагрузке. Из документации PHP я узнал, что:include()
и логом не привели к желаемому результату. convert.base64-decode
внутри функции include()
, чтобы обойти ошибку с символом ?
. Но поскольку у меня была бессонная ночь, мой мозг работал плохо, и я совсем забыл, что в случае с Laravel исследователь злоупотреблял цепочкой функций file_get_contents()
и file_put_contents()
с одинаковыми аргументами внутри, что позволило ему переписать лог. Я также забыл и об этом ограничении:View
с файлом /etc/passwd, разместил его на контролируемом сервере и не увидел в ответе содержимого файла /etc/passwd. Я повторил те же шаги на api-stage.seedr.ru, но там по-прежнему всё работало как надо. \
Нулевой байт
*_file
состоит из 8 символов? Нет, только из 6. Именно это я исправлял каждый раз, и всё отрабатывало без ошибок на api-stage.seedr.ru. Позже в трассировке стека я заметил следующее:_file
равняется NULL, но по какой-то причине у объекта View
ещё есть публичный атрибут *_file
с моей нагрузкой. Возможно, знатоки PHP уже поняли причину такого поведения, но мне пришлось потратить некоторое время на решение этой проблемы. *_file
состоит из 8 символов:unserialize()
публичный атрибут *_file
. Чтобы решить проблему, я просто разместил сериализованную строку с нулевыми байтами на своем сервере. Теперь vimeo.com перенаправлял запросы на мой сервер, где при помощи функции echo()
я отдавал нагрузку с нулевыми символами. После того, как мне удалось загрузить содержимое файла /etc/passwd на api.seedr.ru, я снова вернулся к анализу загруженных логов.Последнее отправление
include()
и получил вывод команды bash. Оставалось только дождаться 21 декабря и свежего лога, потому что логи за 20 декабря для api.seedr.ru и api-stage.seedr.ru были отравлены моими неудачными нагрузками.$argv[1]
на $_GET[1]
после локальных тестов… В ожидании ещё одного дня вспомнил, что сегодня у меня есть ещё одна попытка на api-stage.seedr.ru:
TL;DR
Благодарность:
Полезные ссылки:
P.S.
ссылка на оригинал статьи https://habr.com/ru/post/708384/
Добавить комментарий