
Pre-auth RCE dans React Server Components versions 19.0.0, 19.1.0, 19.1.1, et 19.2.0.
Ce dépôt contient une reproduction de preuve de concept (PoC) de CVE-2025-55182, une vulnérabilité critique de sécurité dans les composants serveur React (RSC) permettant l'exécution de code arbitraire sans authentification.
La vulnérabilité réside dans la manière dont les composants serveur React désérialisent les « Actions serveur » des requêtes client. Plus précisément, la fonction requireModule ne vérifiait pas que le nom d'exportation demandé était une propriété directe du module. Cela permettait aux attaquants d'accéder à la propriété constructor des fonctions exportées, obtenant ainsi une référence au constructeur global Function, qui peut être utilisé pour exécuter du code arbitraire.
Ce PoC utilise un environnement Node.js minimal pour isoler la vulnérabilité dans la bibliothèque react-server-dom-webpack, afin de s'assurer que l'exploit démontre le bogue dans la bibliothèque elle-même, et non une mauvaise configuration dans un framework.
npmnpm install
[!NOTE] Le
package.jsonest fixé à la version vulnérable19.0.0.
Ce script met en place un serveur HTTP brut qui utilise le runtime React vulnérable pour décoder les requêtes.
# tty1
node --conditions react-server server.js
Dans un terminal séparé, exécutez l'exploit. Celui-ci envoie une charge utile Flight malveillante au serveur.
# tty2
node exploit.js id
Vous devriez voir le résultat de la commande retourné dans la réponse :
Sortie attendue :
Response: uid=0(root) gid=0(root) groups=0(root)
Pourquoi la vulnérabilité est-elle survenue ?
La fonction requireModule dans ReactFlightDOMServerNode.js faisait essentiellement confiance à tout name envoyé par le client. Elle exécutait moduleExports[metadata[NAME]] sans vérifier si cette propriété était réellement destinée à être exposée. Donc si le client disait « mec, je veux cette propriété », le serveur répondait « pas de souci, la voilà, mon pote ».
Pourquoi est-ce dangereux de laisser accéder à n'importe quelle propriété ?
Parce que cela permet à quiconque d'atteindre la chaîne de prototypes, y compris le constructor, ce qui est extrêmement dangereux. Si le module exporte une fonction (comme module.exports = () => {}), alors son constructor est littéralement le constructeur global Function.
Pourquoi l'obtention du constructeur Function mène-t-elle à une RCE ?
Une fois qu'un attaquant récupère le constructeur Function, il peut abuser de la fonctionnalité « Action serveur liée ». Il lie une chaîne contenant du JavaScript malveillant à ce constructeur (ce qui revient à créer new Function("code malveillant")). Une fois exécutée, le serveur exécute le code qu'il a injecté.
Pourquoi React exécuterait-il réellement cette fonction malveillante ?
Parce que les Actions serveur peuvent être déclenchées par un ID. Si l'attaquant fabrique une charge utile avec un ID d'action qui pointe vers la référence module#constructor, React la résout comme une action normale et l'exécute. Cette « action » est en fait sa fonction malveillante.
Pourquoi rien de tout cela n'a-t-il été validé ?
Le système supposait simplement que les id et name des métadonnées de référence serveur correspondraient toujours à des exportations valides définies par le développeur. Il n'y avait aucun contrôle de sécurité comme hasOwnProperty pour s'assurer que la propriété demandée était une véritable exportation et non quelque chose hérité de la chaîne de prototypes.
server.js plutôt que Next.js ?J'utilise un server.js brut (et un webpack-runtime.js d'assistance) pour configurer manuellement le runtime des composants serveur React. Cela nous permet de :
module.exports = fn). Un véritable bundler pourrait modifier la façon dont les exportations sont encapsulées, selon sa configuration.react-server-dom-webpack, pas dans Next.js.react-server-dom-webpack suppose qu'il s'exécute dans un bundle Webpack. Notre webpack-runtime.js lui fournit les globales auxquelles il s'attend (__webpack_require__, __webpack_chunk_load__).
Ce n'est pas un simulacre de la vulnérabilité, c'est juste donner à la bibliothèque le runtime minimum dont elle a besoin pour fonctionner.Il y a eu des discussions sur des « PoC invalides » (https://react2shell.com) qui ne fonctionnent que si le développeur expose délibérément des éléments dangereux comme child_process.exec.
Ce PoC n'en fait pas partie. Il fonctionne sur une configuration normale et sûre.
La fonction exposée est inoffensive
L'application expose une simple fonction updateProfile qui renvoie juste une chaîne et rien de suspect, pas de commandes shell.
L'exploit échappe complètement à cette fonction
La vulnérabilité permet à l'attaquant d'ignorer l'exportation sécurisée et de sauter directement à updateProfile.constructor, qui est le constructeur global Function.
Le problème central est l'accès à la propriété
React n'aurait PAS dû autoriser l'accès à .constructor. Le développeur n'avait pas l'intention d'exposer le constructeur Function ; c'est la désérialisation non sécurisée qui l'a fait à sa place.
La seule véritable exigence est que le module exporte une fonction directement (module.exports = fn), ce qui est très courant dans CommonJS et de nombreuses configurations de bundler.
La charge utile dans exploit.js fabrique un message Flight React avec trois morceaux :
id : "user-profile-action#constructor", signifiant « donne-moi le constructeur ».bound : pointe vers le Morceau 2, qui contient les arguments.["console.log('nice try, diddy!')"] : la chaîne de code malveillant.Lorsque React désérialise cela :
user-profile-action..constructor => obtient le Function global.new Function("console.log('nice try, diddy!')")Et voilà la RCE !
Mettez à jour immédiatement vers les versions corrigées :
react-server-dom-webpack >= 19.0.1react-server-dom-parcel >= 19.0.1react-server-dom-turbopack >= 19.0.1Le correctif introduit des vérifications hasOwnProperty pour empêcher l'accès aux propriétés héritées et restreint les téléchargements de fichiers base64.
Si vous exécutez ce PoC sur une version corrigée, le serveur plantera ou renverra une erreur avec :
$ node --conditions react-server server.js
Listening on http://localhost:3000
/path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726
resolvedValue = resolvedValue.bind.apply(
^
TypeError: Cannot read properties of undefined (reading 'bind')
at /path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726:43
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Node.js v20.19.3
Cela confirme que l'exploit n'a pas pu accéder à la propriété constructor (elle a retourné undefined au lieu de Function), et donc l'appel .bind suivant a échoué.
Ce code est fourni à des fins éducatives et de test uniquement. N'utilisez pas cet exploit contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation explicite de test.
Publié sous la DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE.