
Brève analyse de la vulnérabilité RCE des React Server Components
Ces derniers jours, une vulnérabilité RCE par désérialisation dans React a fait grand bruit, le CVSS officiel atteignant directement 10.0, au même niveau que Log4j à l'époque. De nombreuses rumeurs ont rapidement circulé, affirmant qu'il s'agissait du Log4j du développement web moderne, provoquant la panique chez les développeurs de nombreuses entreprises. Ainsi, tout le monde s'est réveillé en se précipitant pour consulter la documentation et appliquer des correctifs... En parallèle, des voix critiques se sont élevées en ligne : après des tests, certains ont constaté que la vulnérabilité n'était pas aussi grave que décrite, et que son exploitation nécessitait certaines conditions. J'ai donc décidé de prendre le temps d'étudier cette vulnérabilité en profondeur.
react-server-dom-webpack < 19.2.0, react-server-dom-turbopack < 19.2.0La cause de cette vulnérabilité est la suivante : dans [email protected], la fonction clé permettant d'analyser une Server Action côté serveur est requireModule (pseudo-code) :
function requireModule(metadata) {
var moduleExports = __webpack_require__(metadata[0]);
// ...
return "*" === metadata[2]
? moduleExports
: "" === metadata[2]
? moduleExports.__esModule
? moduleExports.default
: moduleExports
: moduleExports[metadata[2]]; // ← Point vulnérable
}
Le cœur du problème réside dans la partie moduleExports[metadata[2]] : aucune validation n'est effectuée sur metadata[2], ce qui permet à un attaquant d'accéder non seulement aux propriétés exportées du module, mais aussi aux propriétés de la chaîne de prototypes (telles que constructor, __proto__, etc.). Lorsque l'attaquant construit metadata[0] (par exemple en le pointant vers vm), il peut ensuite construire metadata[2] pour exporter une méthode dangereuse d'un module spécifique, comme vm.runInThisContext, et ainsi exploiter la vulnérabilité.
Pour mon analyse, je me suis référé à l'environnement de test et à l'exploit fournis par ejpir, en prenant l'exemple du gadget vm_runInThisContext (code exécution). Voici le processus (attention : dans un environnement réel, le processus d'exploitation peut différer !) :
Tout d'abord, après avoir envoyé une requête avec le payload, on place un point d'arrêt à l'endroit où la requête est récupérée :


Ensuite, le programme atteint la ligne const formData = parseMultipart(buffer, boundaryMatch[1]);, on entre dans parseMultipart :

parseMultipart extrait les données du corps de la requête et les retourne dans formData :

decodeAction (point d'entrée de la vulnérabilité)On entre dans const actionFn = await decodeAction(formData, serverManifest); Emplacement de la vulnérabilité :

On entre dans loadServerReference :


requireModuleNous arrivons maintenant à l'emplacement central du code vulnérable requireModule, on entre :


La valeur de id est divisée par # : la partie avant le # sert de module, la partie après de méthode, et la valeur de bound est passée comme argument de la méthode :




On entre dans actionFn pour exécuter le payload final :



L'exploitation est terminée !
Cette vulnérabilité est toujours due à un contrôle insuffisant des entrées, comme pour Log4j et fastjson. Dans mon test ci-dessus, j'ai utilisé vm_runInThisContext, mais en réalité cette vulnérabilité dispose de plusieurs gadgets exploitables, par exemple :
vm#runInThisContextvm#runInNewContextchild_process#execSyncchild_process#execFileSyncchild_process#spawnSyncfs#readFileSyncfs#writeFileSync#constructor#__proto__#prototypeUn attaquant peut exploiter cette vulnérabilité pour :
vm#runInThisContext ou child_process#execSync pour exécuter des commandes système arbitrairesfs#readFileSync, fs#writeFileSync pour lire/écrire des fichiers arbitraires.bashrc, remplacement de fichiers d’application, etc..env, clés privées, identifiants de base de données, etc.)Sur cette base, voici les mesures de défense associées :
On peut mettre en place des règles au niveau du WAF pour bloquer ces champs dangereux et ainsi intercepter les attaques malveillantes. On peut également effectuer un filtrage au niveau de nginx, comme suit :
# Exemple de configuration Nginx
location /formaction {
# Bloquer les requêtes contenant des références de modules dangereux
if ($request_body ~* "(vm#|child_process#|fs#|module#)") {
return 403;
}
# Bloquer les tentatives de pollution de la chaîne de prototypes
if ($request_body ~* "(#constructor|#__proto__|#prototype)") {
return 403;
}
}