September 12, 2026
Reactor (HTB) Walkthrough
A nuclear reactor monitoring dashboard running on Next.js, which is already a slightly unsettling combination of words before you’ve even…

By L4ZZ3RJ0D
4 min read
A nuclear reactor monitoring dashboard running on Next.js, which is already a slightly unsettling combination of words before you've even touched the box.
Standard nmap sweep to open.
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
3000/tcp open ppp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| X-Powered-By: Next.js22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
3000/tcp open ppp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| X-Powered-By: Next.jsSSH and a Next.js app on port 3000. Loaded it up and got a genuinely nice looking reactor control panel, core temp, neutron flux, coolant flow, all the dials you'd want if you were pretending to run a nuclear plant for an afternoon.
Ran a spider against it first before touching any brute-force tooling, mostly just to see what was actually linked and reachable without guessing. Confirmed what the visual already suggested, no obvious attack surface sitting in the nav, no extra pages hiding behind a button. Directory fuzzing came up equally quiet. With nothing obvious to click on, the framework itself became the actual target, and checking a few different fingerprinting angles against the Next.js version pointed straight at a known, fairly recent CVE, react2shell, an unauthenticated RCE affecting specific Next.js builds.
Confirmed vulnerable, but the Metasploit module itself didn't actually pop a session. Rather than fight the module further, I went looking for a standalone proof-of-concept script instead, and found one built specifically around this CVE.
That one landed cleanly, dropping into a proper interactive shell as ubuntu, complete with its own little command set for toggling root mode and downloading files. The server threw the occasional 500 error along the way for reasons I never fully chased down, but the shell itself stayed usable regardless.
Landed in /opt/reactor-app, the app's own directory, and immediately checked for a .env file, since Node apps configured this way basically always have one.
An API key and an internal webhook URL, neither of which turned out to be directly useful for anything, no admin panel or separate service anywhere that key actually unlocked. Genuinely hit a wall here for a while, shell access achieved, but no user.txt or flag.txt sitting anywhere obvious, completely stuck at what felt like square one despite technically having code execution.
The thing I'd walked straight past was the app's own database file sitting in the same directory the whole time.
Two accounts, two hashes. Admin's hash didn't crack, but engineer's did, coming out to a PASSWORD. Tried it straight against SSH instead of continuing to poke around inside the limited react2shell session.
User flag down, finally off square one. Checked sudo permissions immediately, and engineer had nothing configured, no easy path there. Went looking for SUID binaries next, standard next move, and came up empty too, nothing obviously abusable. Spent a while stuck again before actually reading through what processes were running on the box rather than just checking static permission misconfigurations.
A root-owned Node process running with --inspect enabled, which exposes Chrome's DevTools Protocol on that port for debugging purposes. That flag existing on a root process at all is basically an open door, since CDP(Chrome DevTools Protocol) lets you remotely evaluate arbitrary JavaScript inside the running process, and this process happened to be running as root. Queried the inspector's own discovery endpoint to get the exact debugger session details.
CDP itself only speaks WebSocket, and I didn't have a convenient WebSocket client sitting on the box, so the actual approach was hand-rolling the WebSocket handshake and frame encoding manually in raw Node, opening a TCP connection, sending the upgrade request myself, then manually constructing a masked WebSocket frame carrying a Runtime.evaluate command.
The expression field there is the actual payload, plain JavaScript telling the root-owned Node process to shell out and read the root flag, executed inside a process that already has root's own permissions. Wrote the full script out, swapped in the correct debugger ID from the /json response, and ran it.
Worked cleanly, root flag printed straight back through the connection. Whole thing in one sentence, a debugging flag left enabled on a root-owned process is functionally the same as leaving a root shell sitting open on localhost, since CDP was never designed with the assumption that an untrusted local user might be able to reach it.
Full chain start to finish:
Next.js react2shell RCE (CVE-2025-55182)
│
â–¼
Low-privileged shell as ubuntu
│
â–¼
Exposed SQLite database in app directory
│
â–¼
Cracked engineer's password hash
│
â–¼
SSH as engineer, grab user.txt
│
â–¼
Found root-owned Node process running --inspect
│
â–¼
Queried CDP /json endpoint for debugger session
│
â–¼
Hand-rolled WebSocket connection to the inspector
│
â–¼
Runtime.evaluate() executes JS as root
│
â–¼
Read /root/root.txtNext.js react2shell RCE (CVE-2025-55182)
│
â–¼
Low-privileged shell as ubuntu
│
â–¼
Exposed SQLite database in app directory
│
â–¼
Cracked engineer's password hash
│
â–¼
SSH as engineer, grab user.txt
│
â–¼
Found root-owned Node process running --inspect
│
â–¼
Queried CDP /json endpoint for debugger session
│
â–¼
Hand-rolled WebSocket connection to the inspector
│
â–¼
Runtime.evaluate() executes JS as root
│
â–¼
Read /root/root.txtGenuinely satisfying box, mostly because of how many times it felt like a dead end right up until it wasn't, RCE that didn't immediately hand over a flag, a wall of no sudo and no SUID binaries, and then a single overlooked ps aux revealing the entire privilege escalation path sitting in plain sight the whole time.
Happy hacking, see you in the next box, hopefully one where nobody leaves a debugger listening on a process that happens to own the whole machine :)