
Analyse technique détaillée de CVE-2025-55182, une RCE critique non authentifiée dans Next.js + React 19.0.0. Documente le parcours de recherche, l'analyse du correctif, la traversée de prototypes et le sink de désérialisation Blob qui permet une exploitation complète sans gadgets spécifiques à l'application.
Ce document décrit mon processus de recherche personnel sur CVE-2025-55182, y compris les découvertes confirmées et les expériences que j'ai menées. Il représente un compte rendu honnête de mon enquête, y compris les hypothèses initiales incorrectes et la percée finale.
Après une enquête approfondie sur CVE-2025-55182 (CVSS 10.0), ma conclusion initiale était que la RCE automatique n'avait pas été démontrée publiquement et que l'exploitation nécessitait des gadgets spécifiques à l'application. Cette conclusion était incorrecte.
Le 5 décembre 2025, après avoir reçu des informations supplémentaires d'un autre chercheur indépendant sur X (@maple3142), j'ai reproduit avec succès une RCE complète sans authentification sur Next.js vanilla sans nécessiter de vulnérabilités de code spécifiques à l'application.
Mon attention initiale portait sur la première modification du correctif React 19.0.1 :
Vulnérable (19.0.0) :
return fn.bind.apply(fn, [null].concat(_ref));
Corrigé (19.0.1) :
if (Array.isArray(promiseValue)) {
promiseValue = promiseValue.slice(0);
} else {
promiseValue = [];
}
J'ai supposé que le chemin d'attaque passait par fn.bind.apply() avec des objets malveillants au lieu de tableaux. J'ai pu démontrer l'injection d'arguments dans les Actions Serveur en utilisant $ACTION_REF_ avec un bound contrôlé par l'attaquant :
curl -X POST http://localhost:9000/ \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"<ACTION_ID>","bound":["; id #","/etc/passwd"]}'
Résultat : Les arguments ont été injectés avec succès dans l'Action Serveur. Cependant, cela ne conduit à une RCE que si la fonction cible utilise ces arguments de manière non sécurisée.
Un examen plus approfondi du correctif a révélé un autre changement important dans getOutlinedModel() :
Vulnérable :
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]];
Corrigé :
if (hasOwnProperty.call(value, name)) {
value = value[name];
}
Ce comportement vulnérable permettait le parcours de la chaîne de prototypes en utilisant des références comme :
$1:__proto__:constructor:constructor
Pendant la recherche, j'ai testé un objet thenable contenant une propriété .then :
{"then": "$1:__proto__:constructor:constructor"}
Lorsque JavaScript traite cela via await :
.then et traite l'objet comme une Promiseobj.then(resolve, reject)then se résout en Function.constructor, JavaScript tente d'exécuter Function(resolve, reject)Résultat observé :
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>)
Lorsque Function.constructor est invoqué comme :
Function(resolve, reject)
// resolve.toString() = "function () { [native code] }"
// Function attempts to parse this as code → SyntaxError
Les arguments resolve et reject sont toujours les fonctions Promise natives. Function tente d'interpréter le premier argument comme du code source, ce qui est du JavaScript invalide.
C'est là que ma recherche a stagné. J'ai conclu que contrôler les arguments de Function.constructor était impossible sans un gadget spécifique à l'application.
Après avoir publié mes premières découvertes, un autre chercheur indépendant m'a indiqué un élément critique que j'avais manqué : le puits de désérialisation $B (Blob).
Dans le code serveur React Flight compilé (non visible dans les sources TypeScript), il existe :
case "B":
return response._formData.get(response._prefix + id);
Emplacement :
[email protected]cjs/react-server-dom-webpack-server.node.unbundled.development.js[email protected]/dist/compiled/react-server-dom-webpack/Ce code permet à React d'appeler response._formData.get() avec des valeurs dérivées d'une entrée contrôlée par l'attaquant, sans validation.
| Approche | Arguments pour Function.constructor | Résultat |
|---|---|---|
| Thenable (Phase 3) | resolve, reject (fonctions natives) | ❌ SyntaxError |
| Blob + Réponse empoisonnée | _prefix (chaîne contrôlée par l'attaquant) | ✅ RCE |
En combinant :
$1:__proto__:then → Chunk.prototype.then)_response empoisonné avec :
_formData.get défini sur Function.constructor_prefix défini sur du code JavaScript arbitraire$BLe gestionnaire case "B": exécute :
Function.constructor("<attacker code>" + id)
Cela contourne complètement la limitation de liaison d'argument.
Les PoCs GitHub populaires prétendant une RCE utilisent des identifiants d'Action tels que :
"child_process#execSync""vm#runInThisContext"Ce sont des faux. Next.js n'accepte que les identifiants d'Action définis par l'application. Les identifiants invalides produisent :
TypeError: Cannot read properties of undefined (reading 'workers')
Cependant, le véritable exploit ne nécessite pas de faux identifiants d'Action. Tout identifiant d'Action Serveur valide fonctionne.
J'ai testé cette chaîne d'exploitation sur une application minimale Next.js 15.0.3 + React 19.0.0 avec seulement :
async function myAction(data) {
"use server";
console.log("Server Action called with:", data);
return { success: true, received: data };
}
Résultat : RCE complète confirmée. L'application ne contenait aucun code non sécurisé, ni eval, ni execSync, ni gadgets.
| Aspect | Résultat |
|---|---|
| Authentification requise ? | ❌ Non |
| Gadget applicatif requis ? | ❌ Non |
| Fonctionne sur Next.js vanilla ? | ✅ Oui |
| Nombre de requêtes nécessaires | 1 POST |
| Versions affectées | Next.js ≤15.0.4 + React 19.0.0 |
| Score CVSS | 10.0 (justifié) |
Au 5 décembre 2025, le puits $B existe toujours dans Next.js 15.0.5 (la version soi-disant corrigée).
Vérification :
$ npm pack [email protected]
$ tar -xzf next-15.0.5.tgz
$ grep -A5 'case "B":' package/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
Résultat :
case "B":
return response._formData.get(response._prefix + obj);
Le code est identique à la version vulnérable.
En raison de la découverte que la vulnérabilité pourrait ne pas être entièrement corrigée dans les versions prétendument "fixées", je retiens la charge utile complète de preuve de concept en attendant une vérification avec les équipes de sécurité de Vercel et Meta.
Les détails techniques fournis dans ce document sont suffisants pour comprendre le mécanisme de la vulnérabilité mais intentionnellement incomplets pour empêcher une exploitation immédiate.