
Preuve de concept d'exploitation pour CVE-2025-55182, démontrant une RCE non authentifiée dans les composants serveur React via une désérialisation non sécurisée des actions serveur, avec des étapes de reproduction détaillées et des conseils d'atténuation.
Ce dépôt contient une reproduction à titre de preuve de concept (PoC) de CVE-2025-55182, une vulnérabilité de sécurité critique dans les serveurs React Server Components (RSC) qui permet l'exécution de code arbitraire sans authentification.
La vulnérabilité réside dans la manière dont React Server Components désérialise les "Server Actions" à partir des requêtes client. Plus précisément, la fonction requireModule ne vérifiait pas que le nom d'export 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 bug dans la bibliothèque elle-même, et non une mauvaise configuration dans un framework.
npmnpm install
[!NOTE] Le fichier
package.jsonest épinglé à 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. Cela envoie une charge utile Flight malveillante au serveur.
# tty2
node exploit.js id
Vous devriez voir le résultat de la commande renvoyé dans la réponse :
Résultat attendu :
Response: uid=0(root) gid=0(root) groups=0(root)
Pourquoi la vulnérabilité est-elle survenue ?
La fonction requireModule dans ReactFlightDOMServerNode.js faisait simplement confiance à tout name envoyé par le client. Elle faisait moduleExports[metadata[NAME]] sans vérifier si cette propriété était réellement destinée à être exposée. Ainsi, si le client disait « mec, je veux cette propriété », le serveur répondait « pas de souci, la voilà, mon pote ».
Pourquoi est-ce une mauvaise idée de laisser les gens accéder à n'importe quelle propriété ?
Parce que cela permet à quiconque de remonter la chaîne de prototypes, même jusqu'au constructor, ce qui est extrêmement dangereux. Si le module exporte une fonction (par exemple 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 s'empare du constructeur Function, il peut abuser de la fonctionnalité "Bound Server Action". Il lie une chaîne contenant du JavaScript malveillant à ce constructeur (ce qui revient à faire new Function("code malveillant")). Et une fois que cela s'exécute, le serveur exécute le code qu'il a inséré.
Pourquoi React exécuterait-il réellement cette fonction malveillante ?
Parce que les Server Actions peuvent être déclenchées par un ID. Si l'attaquant fabrique une charge utile avec un ID d'action qui pointe vers leur référence module#constructor, React la résout comme une action normale et l'exécute. Cette "action" est en réalité leur fonction malveillante.
Pourquoi rien de tout cela n'a-t-il été validé ?
Le système supposait simplement que les id et name provenant des métadonnées de Server Reference feraient toujours référence à des exports valides définis 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 un véritable export 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'aide) pour configurer manuellement le runtime React Server Components. Cela nous permet de :
module.exports = fn). Un véritable bundler pourrait modifier la manière dont les exports sont encapsulés, selon sa configuration.react-server-dom-webpack, et non dans Next.js.react-server-dom-webpack suppose qu'il s'exécute à l'intérieur d'un bundle Webpack. Notre webpack-runtime.js lui fournit les globales qu'il attend (__webpack_require__, __webpack_chunk_load__).Ceci n'est pas un simulacre de la vulnérabilité, cela donne simplement à la bibliothèque le runtime minimum nécessaire pour fonctionner.
Il y a eu des discussions sur les "PoC invalides" 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 ne fait que renvoyer 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'export sécurisé et d'accéder 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 pour lui.
La seule véritable exigence est que le module exporte une fonction directement (module.exports = fn), ce qui est très courant en CommonJS et dans 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 immédiatement à jour 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 contre une version corrigée, le serveur plantera ou générera une erreur comme :
$ 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 renvoyé undefined au lieu de Function), et donc l'appel .bind ultérieur a échoué.
Ce code est uniquement destiné à des fins éducatives et de test. N'utilisez pas cet exploit contre des systèmes qui ne vous appartiennent pas ou pour lesquels vous n'avez pas d'autorisation explicite de test.
Publié sous la licence DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE.