Категория > Новости > HTB OverGraph. Извлекаем данные через цепочку Open Redirect, RXXS и CSTI - «Новости»

HTB OverGraph. Извлекаем данные через цепочку Open Redirect, RXXS и CSTI - «Новости»


12-08-2022, 00:02. Автор: Тарас
Hack The Box.

warning


Под­клю­чать­ся к машинам с HTB рекомен­дует­ся толь­ко через VPN. Не делай это­го с компь­юте­ров, где есть важ­ные для тебя дан­ные, так как ты ока­жешь­ся в общей сети с дру­гими учас­тни­ками.



 

Разведка


 

Сканирование портов


До­бав­ляем IP-адрес машины в /etc/hosts:


10.10.11.157 overgraph.htb

И запус­каем ска­ниро­вание пор­тов.



Справка: сканирование портов


Ска­ниро­вание пор­тов — стан­дар­тный пер­вый шаг при любой ата­ке. Он поз­воля­ет ата­кующе­му узнать, какие служ­бы на хос­те при­нима­ют соеди­нение. На осно­ве этой информа­ции выбира­ется сле­дующий шаг к получе­нию точ­ки вхо­да.


На­ибо­лее извес­тный инс­тру­мент для ска­ниро­вания — это Nmap. Улуч­шить резуль­таты его работы ты можешь при помощи сле­дующе­го скрип­та.


ports=$(nmap -p- --min-rate=500 $1 | grep^[0-9] | cut -d '/' -f 1 | tr 'n' ',' | sed s/,$//)nmap -p$ports -A $1

Он дей­ству­ет в два эта­па. На пер­вом про­изво­дит­ся обыч­ное быс­трое ска­ниро­вание, на вто­ром — более тща­тель­ное ска­ниро­вание, с исполь­зовани­ем име­ющих­ся скрип­тов (опция -A).



Ре­зуль­тат работы скрип­та

От­кры­то два пор­та: 22 — служ­ба OpenSSH 8.2p1 и 80 — веб‑сер­вер Nginx 1.18.0. Nmap показал нам, что выпол­няет­ся редирект на адрес http://graph.htb. Тоже добав­ляем этот адрес в файл /etc/hosts.


10.10.11.157 overgraph.htb graph.htb
Глав­ная стра­ница http://graph.htb

Сайт ока­зал­ся однос­тра­нич­ным, поэто­му нуж­но най­ти новые цели для тес­тирова­ния.


 

Сканирование веб-контента


Поп­робу­ем поис­кать скры­тые катало­ги и фай­лы при помощи ffuf.



Справка: сканирование веба c ffuf


Од­но из пер­вых дей­ствий при тес­тирова­нии безопас­ности веб‑при­ложе­ния — это ска­ниро­вание методом перебо­ра катало­гов, что­бы най­ти скры­тую информа­цию и недос­тупные обыч­ным посети­телям фун­кции. Для это­го мож­но исполь­зовать прог­раммы вро­де dirsearch и DIRB.


Я пред­почитаю лег­кий и очень быс­трый ffuf. При запус­ке ука­зыва­ем сле­дующие парамет­ры:




  • -w — сло­варь (я исполь­зую сло­вари из набора SecLists);


  • -t — количес­тво потоков;


  • -u — URL;


  • -fc — исклю­чить из резуль­тата отве­ты с кодом 403.


ffuf -u 'http://graph.htb/FUZZ' -t 256 -wdirectory_2.3_medium_lowercase.txt
Ре­зуль­тат ска­ниро­вания катало­гов с помощью ffuf

И не находим ничего инте­рес­ного, даже в фай­ле server-status. Поэто­му поп­робу­ем прос­каниро­вать под­домены, для чего сно­ва будем исполь­зовать ffuf. К парамет­рам добавим заголов­ки -H и --fs, это поможет отсе­ять стра­ницы по раз­меру.


ffuf -u 'http://graph.htb/' -t 256 -wsubdomains-top1million-110000.txt -H 'Host: FUZZ.graph.htb' --fs 178
Ре­зуль­тат ска­ниро­вания под­доменов с помощью ffuf

И находим новый под­домен internal. Добав­ляем его в файл /etc/hosts.


10.10.11.157 overgraph.htb graph.htb internal.graph.htb

Но, открыв сайт в бра­узе­ре, сра­зу натыка­емся на фор­му авто­риза­ции.


Фор­ма авто­риза­ции http://internal.graph.htb

Так как всю работу про­водим через Burp, то обна­ружим в Burp History обра­щение еще к одно­му домену — internal-api.graph.htb.


Ло­ги Burp History

До­бав­ляем еще одну запись в файл /etc/hosts и затем откры­ваем стра­ницу /graphql.


10.10.11.157 overgraph.htb graph.htb internal.graph.htb internal-api.graph.htb
Глав­ная стра­ница сай­та http://internal-api.graph.htb

На стра­нице исполь­зует­ся GraphQL. Это язык зап­росов, с помощью которо­го кли­ент­ские при­ложе­ния работа­ют с дан­ными. «Схе­мы» GraphQL поз­воля­ют орга­низо­вывать соз­дание, чте­ние, обновле­ние и уда­ление дан­ных в при­ложе­нии. Давай получим дан­ные __schema и отфиль­тру­ем наз­вания типов, это мож­но сде­лать, передав в парамет­ре query сле­дующий зап­рос:


{__schema{types{name,fields{name}}}}
От­вет сер­вера
От­вет сер­вера (про­дол­жение)

На этом пока все, но мы еще не ска­ниро­вали катало­ги на новом домене. Поп­робу­ем сде­лать это. Но, как толь­ко мы обра­тим­ся к любой стра­нице, получим ответ, что зап­росы GET не под­держи­вают­ся. Поэто­му будем ска­ниро­вать зап­росом POST. А так как на домене кру­тит­ся API, то и исполь­зовать будем соот­ветс­тву­ющий сло­варь.


ffuf -u 'http://internal-api.graph.htbFUZZ' -t 256 -XPOST -wapiscan.txt
Ре­зуль­тат ска­ниро­вания API с помощью ffuf

И находим три новые стра­ницы, с которы­ми нач­нем работу.


 

Точка входа


Итак, мы име­ем сле­дующие API:




  • register — для регис­тра­ции поль­зовате­ля;


  • verify — пред­положи­тель­но для про­вер­ки при регис­тра­ции;


  • code — пока непонят­но, но, ско­рее все­го, для про­вер­ки кода, отправ­ленно­го на email.


Я начал со стра­ницы /api/register. Переда­ем наибо­лее веро­ятные парамет­ры: имя поль­зовате­ля, пароль и адрес элек­трон­ной поч­ты.


{
"username":"ralf",
"email":"ralf@graph.htb",
"password":"ralf"
}
По­пыт­ка регис­тра­ции поль­зовате­ля

Но в ответ нам говорят, что у нас невер­ный email или он не верифи­циро­ван. Это инте­рес­но, так как у нас оста­ется все­го две стра­ницы для регис­тра­ции. Видимо, стра­ница /api/code нуж­на для получе­ния кода. Отпра­вим туда свой email.


{
"email":"ralf@graph.htb"
}
По­луче­ние кода

И нам сооб­щают, что четыре циф­ры были отправ­лены на ука­зан­ный поч­товый ящик. По тес­товому сооб­щению на стра­нице /api/verify узна­ем, что вмес­те с поч­той нуж­но при­сылать и код.


Зап­рос к /api/verify

Я поп­робовал переб­рать этот код с помощью Burp Intruder, бла­го ком­бинаций все­го 10 000. Но уже на одном из пер­вых зап­росов все лома­ется, так как мы пре­выси­ли количес­тво попыток!


Со­обще­ние о пре­выше­нии количес­тва зап­росов

Я очень дол­го про­сидел на этом эта­пе — приш­лось даже про­сить под­сказ­ки у дру­зей. Мне посове­това­ли углу­бить­ся в механизм про­вер­ки кода. Тог­да, пот­ратив еще нем­ного вре­мени, я нашел NoSQL-инъ­екцию, которая поз­воля­ет верифи­циро­вать поч­ту, пре­дос­тавляя неп­равиль­ный код. В дан­ном зап­росе мы получим положи­тель­ный резуль­тат, если код не равен 0000.


"email":"ralf@graph.htb","code":{"$ne":"0000"}}
Ве­рифи­кация поч­ты

При­ходит под­твержде­ние того, что поч­та верифи­циро­вана. Пов­торим регис­тра­цию и получим сооб­щение, что пароль и его под­твержде­ние не сов­пада­ют.


По­пыт­ка регис­тра­ции поль­зовате­ля

Тог­да я переп­робовал раз­ные име­на поля под­твержде­ния пароля и опре­делил, что в дан­ном слу­чае под­ходит confirmPassword.


Ре­гис­тра­ция поль­зовате­ля

И акка­унт соз­дан! Перей­дем к фор­ме авто­риза­ции на вто­ром домене и авто­ризу­емся.


Глав­ная стра­ница http://internal.graph.htb

А во вхо­дящих находим сооб­щение от поль­зовате­ля Sally.


Вхо­дящие сооб­щения

Нас про­сят прис­лать ссыл­ку. Поп­робу­ем открыть локаль­ный сер­вер и ски­нуть ссыл­ку на него. В ито­ге при­ходит зап­рос.


Ло­ги веб‑сер­вера Python 3

Да­вай пос­мотрим, как это мож­но исполь­зовать.


 

Точка опоры


Ес­ли еще раз взгля­нуть на стра­ницу, мож­но заметить над меню над­пись null null. В исходном коде есть отсылка к нашему поль­зовате­лю. А в локаль­ном хра­нили­ще бра­узе­ра (F12 → Application) най­дем запись, что это firstname и lastname.


Ис­ходный код стра­ницы
Ло­каль­ное хра­нили­ще бра­узе­ра

Пе­рей­дем в нас­трой­ки про­филя и уви­дим то же самое, толь­ко с воз­можностью изме­нить эти зна­чения.


Стра­ница Profile 

CSTI


Над­пись null null натол­кну­ла меня на мысль об исполь­зовании шаб­лонов. Давай про­ведем базовый тест.


Но­вые зна­чения име­ни поль­зовате­ля
HTB OverGraph. Извлекаем данные через цепочку Open Redirect, RXXS и CSTI - «Новости»
Отоб­ражение име­ни поль­зовате­ля

Как мож­но уви­деть, вмес­то вве­ден­ной стро­ки получа­ем резуль­таты выраже­ний, а зна­чит, есть уяз­вимость в шаб­лонах! Вот толь­ко в локаль­ном хра­нили­ще эти зна­чения хра­нят­ся, как и вво­дились. Зна­чит, шаб­лон работа­ет на кли­ент­ской сто­роне, а это уже путь для CSTI — инъ­екции шаб­лонов на сто­роне кли­ента.


Ло­каль­ное хра­нили­ще бра­узе­ра

Так­же я обра­тил вни­мание на параметр admin со зна­чени­ем false. Я изме­нил на true и перезаг­рузил стра­ницу. В меню появи­лась гра­фа Upload.


Из­менен­ное меню

Толь­ко вот фор­ма заг­рузки не дает заг­рузить файл. Если вер­немся к нашей схе­ме GraphQL, то можем пос­мотреть на необ­ходимые парамет­ры, к при­меру adminToken.


Па­рамет­ры из схе­мы GraphQL

Та­ким обра­зом, нам нужен adminToken поль­зовате­ля Sally. Но получить его неп­росто. Тут появил­ся сле­дующий план: если зас­тавим целево­го поль­зовате­ля выпол­нить зап­рос на сме­ну име­ни (по ссыл­кам же он перехо­дит!), то в качес­тве нового име­ни уста­новим наг­рузку CSTI, переда­ющую нам adminToken. В исходни­ках видим исполь­зование AngularJS.


Ис­ходный код стра­ницы

AngularJS — это популяр­ная биб­лиоте­ка jаvascript, которая ска­ниру­ет HTML на пред­мет тегов с атри­бутом ng-app (дирек­тива AngularJS). Ког­да дирек­тива добав­ляет­ся в тег, появ­ляет­ся воз­можность выпол­нять выраже­ния jаvascript в двой­ных фигур­ных скоб­ках.


Уяз­вимость Template Injection воз­ника­ет, ког­да при­ложе­ние, исполь­зуя какой‑нибудь шаб­лониза­тор, динами­чес­ки внед­ряет поль­зователь­ский ввод в веб‑стра­ницу. Ког­да стра­ница отоб­ража­ется, фрей­мворк ищет в стра­нице шаб­лонное выраже­ние и выпол­няет его. Основное отли­чие CSTI от SSTI зак­люча­ется в том, что при CSTI мы можем добить­ся лишь выпол­нения про­изволь­ного кода на jаvascript. Две самые популяр­ные наг­рузки для CSTI в AngularJS:


{{$on.constructor('alert(1)')()}}
Но­вое имя поль­зовате­ля

Об­новля­ем стра­ницу и пер­вым делом видим окош­ко алер­та.


Вы­зов alert(1) при заг­рузке стра­ницы

А теперь поп­робу­ем эксфиль­тро­вать токен, для чего соз­дадим у себя в хра­нили­ще тес­товый.


Ло­каль­ное хра­нили­ще бра­узе­ра

В качес­тве наг­рузки будем исполь­зовать зна­мени­тый сти­лер, который похища­ет дан­ные через кар­тинку, а дос­туп к хра­нили­щу получим через window.localStorage.


Об­новля­ем стра­ницу и в логах локаль­ного веб‑сер­вера находим зна­чение тес­тового токена.


Ло­ги веб‑сер­вера

Наг­рузка для эксфиль­тра­ции готова, теперь раз­берем­ся, как под­сунуть поль­зовате­лю наш код.


 

Open Redirect


Я сно­ва прос­мотрел все сай­ты и на самом глав­ном домене нашел что‑то вро­де редирек­та.


Код глав­ной стра­ницы http://graph.htb

Ес­ли сущес­тву­ет GET-параметр redirect, то фун­кция window.location.replace уста­новит в качес­тве содер­жимого текущей стра­ницы код, взя­тый по ссыл­ке из redirect. Бла­го мы можем вста­вить вмес­то URL код на jаvascript:


http://graph.htb/?redirect=jаvascript:alert(1)
Вы­пол­нение кода через jаvascript URL

Ос­талось разоб­рать­ся с дан­ными, которые отправ­ляют­ся для изме­нения име­ни поль­зовате­ля.


 

GraphQL


В Burp History най­дем зап­рос, которым мы изме­нили собс­твен­ное имя.


Зап­рос на изме­нение про­филя

Один из парамет­ров — id поль­зовате­ля, а это нем­ного усложня­ет задачу. Сно­ва вер­немся к GraphQL и пос­мотрим, какой из типов содер­жит поле Assignedto.


Тип task

Нас инте­ресу­ет тип task, который мы можем получить зап­росом tasks.


Тип Query

Та­ким обра­зом, нам нуж­но выпол­нить зап­рос tasks с парамет­ром username, в котором мы переда­дим имя поль­зовате­ля Sally. Нас инте­ресу­ет толь­ко поле Assignedto.



Перейти обратно к новости