В этой статье я рас­ска­жу о том, как я свя­зал в цепоч­ку нес­коль­ко проб­лем безопас­ности с целью уда­лен­ного выпол­нения кода (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.

 

Возможные сценарии

В тот момент у себя в голове я уже видел три воз­можных сце­нария ата­ки:

  1. Фаз­зинг фун­кции file_get_contents() с целью добить­ся сле­пой SSRF, то есть выпол­нить HTTP-зап­рос на под­кон­троль­ный мне ресурс и в теории добить­ся небезо­пас­ной десери­али­зации.
  2. Най­ти кон­тро­лиру­емый ответ на vimeo.com и добить­ся небезо­пас­ной десери­али­зации.
  3. Най­ти откры­тый редирект на vimeo.com → SSRF → небезо­пас­ная десери­али­зация.

Пос­ле нес­коль­ких часов раз­личных модифи­каций GET-парамет­ра vid и локаль­ного фаз­зинга фун­кции file_get_contents() я не нашел ничего полез­ного и парал­лель­но решил поделить­ся всей име­ющей­ся информа­цией об этой наход­ке с нес­коль­кими надеж­ными товари­щами.

Итак, пер­вый сце­нарий не сра­ботал, поэто­му я перешел к сле­дующе­му — кон­тро­лиру­емо­му отве­ту на vimeo.com.

Эн­дпо­инт с кон­тро­лиру­емым отве­том дол­жен отве­чать сле­дующим тре­бова­ниям:

  • код отве­та HTTP — 200 OK;
  • дос­тупен для неав­торизо­ван­ного поль­зовате­ля;
  • кон­тро­лиру­емая стро­ка дол­жна находить­ся в начале тела отве­та (PHP успешно десери­али­зует {VALID_SER_STRING}TRASH);
  • кон­тро­лиру­емая стро­ка дол­жна под­держи­вать сим­волы { }, "", необ­ходимые для хра­нения сери­али­зован­ных объ­ектов.

Ни­же пред­став­лены некото­рые из моих попыток най­ти тре­буемое поведе­ние на 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
https://127.0.0.1:22
https://127.0.0.1:22
http://127.0.0.1:25
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.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии