CVE-2025-55182 est une vulnérabilité critique permettant l'exécution de code à distance avant authentification dans React Server , avec un score CVSS de 10,0, soit le niveau de gravité le plus élevé possible. Dans le cadre du programme de boursesOPSWAT , nos boursiers ont mené une analyse technique approfondie de cette vulnérabilité, en examinant sa cause profonde dans le protocole de désérialisation React Flight, la chaîne d'exploitation complète et son impact généralisé sur l'écosystème web moderne. Cet article présente nos conclusions ainsi que des conseils concrets à l'intention des défenseurs.

React est devenu l'une des bibliothèques front-end les plus utilisées au monde, servant de base à une part importante des mobile web et mobile modernes. Les enquêtes menées auprès des développeurs par Stack Overflow classent régulièrement React parmi les meilleurs frameworks web, avec un taux d'adoption dépassant 40 % des développeurs professionnels à l'échelle mondiale. Parallèlement à cette croissance, l'équipe React a introduit Server React Server (RSC) comme fonctionnalité centrale de React 19 — un changement de paradigme qui déplace la logique de rendu du client vers le serveur, permettant ainsi des performances optimisées et une intégration plus étroite entre le code côté serveur et le code côté client.
Cependant, cette évolution architecturale a introduit une nouvelle surface d'attaque critique. Le 29 novembre 2025, le chercheur en sécurité Lachlan Davidson a signalé une vulnérabilité dans la logique de désérialisation côté serveur de React au programme Bug Bounty de Meta. Rendu public le 3 décembre 2025 sous le numéro CVE-2025-55182, ce défaut permet l'exécution de code à distance sans authentification via une simple requête HTTP spécialement conçue. Classée CWE-502 (désérialisation de données non fiables), cette vulnérabilité ne nécessite aucune authentification, aucune interaction de l'utilisateur et aucune configuration particulière de l'application : un déploiement par défaut de type « create-next-app » destiné à la production est immédiatement exploitable.

L'impact a été immédiat et grave. Dans les 48 heures qui ont suivi la divulgation publique, de multiples campagnes d'exploitation ont été observées dans la nature. Selon la Fondation Shadowserver, plus de 77 000 adresses IP publiques ont été identifiées comme potentiellement vulnérables. La télémétrie de Cloudflare a enregistré plus de 582 millions de tentatives d'exploitation au cours de la semaine qui a suivi la divulgation, avec une intensité d'attaque moyenne de plus de 3 500 adresses IP sources uniques par heure et un pic de 16 585 adresses IP attaquantes simultanées. Wiz Research a signalé que 39 % des environnements cloud contenaient des instances vulnérables. La CISA a ajouté cette vulnérabilité à son catalogue des vulnérabilités connues exploitées (KEV) le 5 décembre 2025.
Les cybercriminels ont agi avec une rapidité et une diversité remarquables. Trend Micro a recensé plusieurs campagnes — notamment celles des botnets « emerald » et « nuts » — déployant des balises Cobalt Strike, des implants Sliver, l’agent de surveillance Nezha, des tunnels Fast Reverse Proxy (FRP) et une nouvelle charge utile baptisée « Secret-Hunter », qui exploite des outils open source de collecte d’identifiants tels que TruffleHog et Gitleaks. Threat Intelligence Google Threat Intelligence a identifié des groupes de menaces distincts liés à la Chine (UNC6600, UNC6586, UNC6588, UNC6603) déployant des outils spécialisés — notamment le tunnel MINOCAT, le téléchargeur SNOWLIGHT, la porte dérobée COMPOOD et la porte dérobée HISONIC — aux côtés d’acteurs liés à l’Iran et de groupes à motivation financière menant des campagnes de minage de cryptomonnaies. AWS a documenté des groupes liés à la Chine expérimentant du code d'exploitation dès le 4 décembre, avant même que le code de preuve de concept complet ne soit accessible au public.
À propos Server React
React est une bibliothèque JavaScript destinée à la création d'interfaces utilisateur, maintenue par Meta et une vaste communauté open source. Server React (RSC), introduits avec React 19, marquent un changement fondamental dans la manière dont les applications React gèrent le rendu. Contrairement aux composants client traditionnels qui s'exécutent entièrement dans le navigateur, les composants serveur s'exécutent sur le serveur, produisant une représentation sérialisée de l'interface utilisateur qui est transmise au client. Cette conception réduit la quantité de code JavaScript envoyée au navigateur, améliore les métriques de temps de réponse et permet un accès direct aux ressources côté serveur telles que les bases de données et les systèmes de fichiers.

RSC s'appuie sur un protocole de sérialisation personnalisé appelé « Flight » pour encoder et transmettre des données entre le client et le serveur. Lorsqu'un client invoque une Server (anciennement appelée « Server ), le navigateur regroupe les arguments de la fonction dans une requête HTTP structurée au format Flight. Le serveur désérialise cette charge utile, exécute la fonction demandée et renvoie le résultat au client. Ce couplage étroit entre le client et le serveur (bien qu'élégant sur le plan architectural) signifie que toute faille dans la logique de désérialisation peut avoir des conséquences immédiates et catastrophiques, comme le démontre CVE-2025-55182.
Cette vulnérabilité touche non seulement React lui-même, mais aussi l'ensemble de l'écosystème des frameworks qui s'appuient sur lui. Next.js (qui a fait l'objet d'un avis distinct, CVE-2025-66478, rejeté par la suite car considéré comme un doublon), React Router, Waku, le plugin RSC de Parcel, le plugin RSC de Vite et RedwoodSDK sont tous concernés. Même les applications qui ne définissent pas explicitement Server peuvent être vulnérables si la prise en charge de RSC est activée dans le framework.
Contexte technique
Avant d'examiner la vulnérabilité, il convient de rappeler trois concepts fondamentaux qui sous-tendent la chaîne d'exploitation : le comportement de la fonction `await` de JavaScript avec les objets `thenable`, la traversée de la chaîne de prototypes et le modèle de données par blocs du protocole React Flight.
Les objets `await` et `Thenable` en JavaScript
L'opérateur `await` suspend l'exécution d'une fonction asynchrone jusqu'à ce que l'expression attendue soit résolue. Lorsque `await` rencontre une Promise native, il attend sa résolution et renvoie la valeur obtenue. Cependant, `await` n'exige pas nécessairement une Promise native : tout objet doté d'une méthode `.then()`, appelé « thenable », est traité comme une construction de type Promise.
Lorsque `await` rencontre un objet `thenable`, il appelle la méthode `.then()` de cet objet, en lui transmettant les callbacks `resolve` et `reject` fournis par le système. La valeur transmise à `resolve` devient le résultat de l'expression `await`. Il est essentiel de noter que si la valeur résolue est elle-même un thenable, la méthode .then() de cet objet imbriqué est appelée de manière récursive jusqu'à ce qu'une valeur primitive ou une Promise stabilisée soit atteinte. Ce comportement de résolution récursive est au cœur de l'exploitation de la vulnérabilité CVE-2025-55182.
Parcours de la chaîne de prototypes
Chaque objet JavaScript dispose d'un lien interne vers son prototype, accessible via la propriété __proto__. Lorsqu'on accède à une propriété d'un objet, le moteur JavaScript vérifie d'abord les propriétés propres à cet objet. Si la propriété n'est pas trouvée, le moteur parcourt la chaîne de prototypes — en remontant à travers chaque lien __proto__ — jusqu'à ce que la propriété soit trouvée ou que la chaîne se termine par une valeur indéfinie.
Un attaquant peut exploiter ce mécanisme d'héritage pour accéder à des propriétés situées au-delà de la portée prévue d'un objet. En intégrant __proto__ dans les chemins d'accès aux propriétés, un attaquant peut atteindre des méthodes et des constructeurs internes que l'application n'avait jamais prévu d'exposer. En JavaScript, l'expression obj.__proto__.constructor.constructor renvoie le constructeur global Function, qui peut créer et exécuter des fonctions arbitraires à partir d'une chaîne de caractères fournie en entrée.
Le protocole React Flight et le modèle de données par blocs
When a client invokes a Server Function, the browser sends an HTTP POST request with a multipart/form-data body. Each form field contains a numbered “chunk” of serialized data. The Flight protocol uses special string prefixes to encode data types: $<id> references the resolved value of another chunk, $@<id> references the raw chunk object itself, $W<id> represents a Set, $K<id> represents FormData, and $B<id> triggers the blob handler.
Prenons une Server définie comme suit :

La requête HTTP correspondante contient plusieurs champs de formulaire, chacun composé d'une clé et d'une valeur : le champ 0 contient le tableau d'arguments avec des références telles que « $W1 » et « $K2 », tandis que les champs 1 et 2_* contiennent les données auxquelles ces références renvoient. Le serveur traite chaque champ au fur et à mesure de son arrivée, en stockant les résultats intermédiaires dans des objets appelés « chunks ».

Un chunk est un objet « thenable » doté de quatre propriétés clés : status (l'état de résolution), value (les données stockées), reason (les informations d'erreur) et _response (une référence vers l'objet de réponse parent). Lorsque le serveur rencontre un chunk en attente, la méthode .then() du chunk est invoquée. Si le statut du chunk est INITIALIZED, la fonction de rappel resolve reçoit chunk.value. Si le statut est PENDING, BLOCKED ou CYCLIC, les fonctions de rappel sont mises en file d'attente pour une exécution ultérieure.

Le chunk 0 représente généralement le tableau d'arguments de la Server appelée. Une fois que tous les champs du formulaire ont été reçus et que toutes les références internes ont été résolues, chunk_0.value contient le tableau d'arguments entièrement constitué, qui est ensuite transmis à la fonction cible.
Traitement des requêtes de bout en bout (Next.js → désérialisation React Flight)
Ce qui suit décrit comment Next.js traite une requête Server entrante dans des conditions normales, depuis la couche HTTP jusqu'au moteur de désérialisation React Flight.

Fonction handleAction() - Next.js
Lorsqu'une Server est appelée, la requête est transmise à la fonction `handleAction`. Cette fonction valide les métadonnées, vérifie les en-têtes et les jetons CSRF, et s'assure que la requête correspond à une action de récupération valide. Un flux appelé busboyStream est ensuite créé pour analyser le corps du formulaire multipart. La fonction decodeReplyFromBusboy lie des émetteurs d'événements à ce flux, déclenchant les fonctions de gestion de désérialisation ServerReact à mesure que les données brutes sont reçues. La valeur de retour de decodeReplyFromBusboy est chunk_0; l'opérateur await la résout et transmet sa valeur assemblée à la Server invoquée.

La fonction getChunk renvoie le bloc correspondant à un identifiant donné. Si ce bloc n'existe pas encore, elle crée soit un ResolvedModelChunk (si des données sont déjà présentes dans response._formData), soit un PendingChunk (si aucune donnée n'est encore arrivée pour cet identifiant).

Lorsque la fonction `decodeReplyFromBusboy` renvoie `chunk_0`, le bloc se trouve toujours à l'état PENDING. L'opérateur `await` appelle `chunk_0.then()` et stocke temporairement les callbacks `resolve` et `reject` respectivement dans `chunk_0.value` et `chunk_0.reason`. Ces callbacks sont réactivés par la fonction `wakeChunk` une fois la résolution de la référence terminée.

Processus de désérialisation - React Server
Lorsque busboyStream reçoit un champ de données brutes complet, il déclenche l'émetteur d'événements « field », appelle la fonction resolveField et lance la désérialisation, qui consiste à convertir les données brutes du formulaire en objets JavaScript entièrement construits. Les fonctions suivantes régissent ce processus.
resolveField(response, key, value)

La clé et la valeur sont ajoutées à response._formData. La fonction récupère ensuite le bloc correspondant à l'ID correspondant à la clé. Si ce bloc existe déjà, la fonction resolveModelChunk est appelée pour le reconstruire. Cette résolution différée est nécessaire car une valeur peut contenir des références à des champs dont les données brutes ne sont pas encore arrivées ; dans ce cas, le Server React Server un PendingChunk avec des callbacks resolve et reject personnalisés pour traiter ces références ultérieurement.
resolveModelChunk(chunk, valeur, id)

La fonction `resolveModelChunk` crée un objet `ResolvedModelChunk` avec l'état `RESOLVED_MODEL` et y injecte les données brutes. Elle reconstitue ensuite le bloc via `initializeModelChunk`, puis appelle `wakeChunk` pour déclencher les callbacks de résolution et de rejet mis en file d'attente, achevant ainsi la résolution de l'objet ou de la référence.
initialiserLeBlocDeModèle(bloc)

La fonction `initializeModelChunk` fait passer l'état du chunk à CYCLIC — indiquant que la résolution des références est en cours — et lance la désérialisation. Elle construit un objet JavaScript brut à partir de `chunk.value` à l'aide de `JSON.parse`, puis transmet cet objet à la fonction `reviveModel`.
reviveModel(réponse, objetParent, cléParent, valeur, référence)

La fonction reviveModel traite de manière récursive chaque composant de l'objet analysé. Lorsqu'elle rencontre une valeur de type chaîne, elle appelle la fonction parseModelString pour la traiter.
parseModelString(response, obj, key, value, reference)

La fonction `parseModelString` traite les chaînes en fonction de leur préfixe afin de gérer différents types d'encodage. Pour les références commençant par `$`, la fonction `getOutlinedModel` est appelée afin de résoudre la référence inter-blocs.
getOutlinedModel(response, reference, parentObject, key, map)

La fonction `getOutlinedModel` divise la référence au niveau du délimiteur « : » pour former un chemin d'accès à la propriété, puis parcourt ce chemin sur l'objet « chunk » cible afin de renvoyer la valeur référencée. Comme détaillé dans l'analyse de la vulnérabilité ci-dessous, l'absence de validation de ces noms de propriétés est précisément à l'origine de la vulnérabilité.
Analyse de la vulnérabilité
Cause profonde
CVE-2025-55182 originates from insufficient input validation in the getOutlinedModel() function within React’s server-side Flight reply handler (ReactFlightReplyServer.js). When a chunk reference includes a property path - such as $<id>:<prop1>:<prop2> - the function resolves it by traversing the specified properties on the target chunk object, computing the result as chunk[prop1][prop2].

La faille critique réside dans le fait que ces noms de propriétés ne sont jamais validés. Le serveur ne vérifie pas si les propriétés demandées sont des propriétés propres à l'objet ou des propriétés de prototype héritées. Un attaquant peut donc inclure __proto__ dans le chemin d'accès à la propriété pour parcourir la chaîne de prototypes et atteindre des méthodes internes qui ne devraient jamais être accessibles à partir d'une entrée contrôlée par l'utilisateur. Par exemple, la référence $1:__proto__:then se résout en Chunk.prototype.then, une fonction que l'attaquant peut alors invoquer avec des arguments contrôlés.

Code vulnérable
La chaîne d'exploitation exploite deux chemins de code distincts dans la logique de désérialisation Flight de React.
Le premier est Chunk.prototype.then, qui définit le comportement des chunks en tant que thenables. Lorsque l'opérateur await est appliqué à un chunk à l'état INITIALIZED, la méthode resolve(chunk.value) est appelée. Si chunk.value est lui-même un thenable (un objet doté d'une méthode .then() ), l'opérateur await appelle de manière récursive chunk.value.then(). C'est grâce à cette résolution récursive qu'un attaquant peut rediriger l'exécution vers une fonction arbitraire.
Le deuxième est le gestionnaire de préfixe $B (blob) dans la fonction parseModelString() :

Dans le cas $B, la fonction appelle response._formData.get(response._prefix + id). Les propriétés _formData.get et _prefix appartiennent toutes deux à l'objet _response stocké dans le chunk. En manipulant ces propriétés via la traversée de la chaîne de prototypes, un attaquant peut rediriger cet appel afin d'invoquer le constructeur global Function en lui passant un code arbitraire en argument.
Exploitation
Through prototype chain traversal, an attacker reaches the global Function constructor via the path <any_object>.constructor.constructor. Because Chunk.prototype.then is a function, the path $1:constructor:constructor resolves to the global Function constructor, which accepts a string and returns a callable function containing that code.

Afin de démontrer l'impact potentiel dans la réalité, nos chercheurs ont mis au point une charge utile de validation de principe conforme aux analyses documentées de manière indépendante par plusieurs équipes de recherche en sécurité. L'exploitation se déroule en deux étapes :
Étape 1 - Créer le faux bloc :
The object delivered in field 0 acts as a fake chunk. Its then property is set to Chunk.prototype.then via the reference path $1:__proto__:then, allowing the Flight deserialization engine to invoke prototype-level behavior on this attacker-constructed object. The _response._formData.get property is pointed at the global Function constructor via $1:constructor:constructor, and _response._prefix is set to the malicious JavaScript code. The value field contains the string {"then": "$B0"}, instructing the blob handler to invoke itself on the same chunk when resolved. The status field is set to resolved_model so that initializeModelChunk is triggered when .then() is called, causing value to be parsed and the blob handler to fire.
Comme le champ 1 n'a pas encore été reçu à ce stade, le Server React crée Server des callbacks « resolve » et « reject » pour gérer la référence en attente.
Étape 2 - Résolution du déclencheur :
Une fois que le champ 1 — contenant « $@0 », une référence brute au bloc 0 — est transmis, le bloc en attente est résolu et pointe directement vers le faux bloc. Cela déclenche la fonction `wakeChunk`, qui traite les callbacks mis en file d'attente et lance la traversée de la chaîne de prototypes lors de la résolution de la référence. Une fois le faux chunk entièrement résolu, wakeChunk est à nouveau appelé. Comme le callback de résolution du faux chunk est la fonction de résolution implicite de Node.js, il invoque la méthode .then() du chunk et résout sa valeur, ce qui aboutit à la construction et à l'exécution du code malveillant injecté via le constructeur Function.
L'exploitation complète ne nécessite qu'une seule requête HTTP :

Replacing {{COMMAND}} with any JavaScript code executes it on the server. The reason: -1 field prevents a toString() error during processing. The Next-Action header may contain any arbitrary value - even x - because the vulnerable deserialization occurs before the server validates the requested Server Function. This is what makes the vulnerability pre-authentication: the payload is processed during the deserialization phase, before any application-level authentication or authorization logic is reached.
En cas d'exploitation réussie, un attaquant obtient un accès complet au contexte d'exécution Node.js sur le serveur, y compris l'accès à `child_process` pour l'exécution de commandes shell, aux variables d'environnement contenant les identifiants de base de données et API , au système de fichiers local, ainsi qu'aux points de terminaison de métadonnées cloud permettant un déplacement latéral.
Preuve de concept
Nos chercheurs ont reproduit cette vulnérabilité dans un environnement de laboratoire contrôlé à l'aide d'une application Next.js standard générée avec create-next-app et configurée pour la production, sans aucune modification de la configuration par défaut. Cette reproduction a confirmé que la charge utile d'exploitation en une seule requête décrite ci-dessus permettait d'exécuter du code à distance de manière fiable.


La démonstration contrôlée a montré qu'un attaquant disposant d'un accès réseau à un serveur Next.js vulnérable peut exécuter du code Node.js arbitraire — notamment créer un shell inversé via `child_process.exec()`, lire les variables d'environnement et accéder au système de fichiers local — sans fournir d'identifiants ni déclencher de contrôles d'authentification au niveau de l'application. La valeur arbitraire acceptée pour l'en-tête Next-Action confirme en outre la nature pré-authentification de la faille : le serveur traite et désérialise la charge utile avant d'effectuer toute recherche d'action ou tout contrôle d'autorisation.
Atténuation
L'équipe React a publié des correctifs le 3 décembre 2025, le jour même où la vulnérabilité a été rendue publique. Les versions corrigées sont disponibles sous les numéros React 19.0.1, 19.1.2 et 19.2.1. Le correctif ajoute une validation stricte des propriétés dans getOutlinedModel() et reviveModel(), bloquant explicitement la résolution des propriétés de prototype héritées — notamment __proto__, constructor et prototype — à partir des chemins de référence contrôlés par l'utilisateur dans les charges utiles Flight.
Les organisations devraient prendre les mesures suivantes sans délai :
- Mettez à jour les paquets React vers une version corrigée (19.0.1, 19.1.2 ou 19.2.1) en exécutant la commande npm install react-server-dom-webpack@latest, react-server-dom-parcel@latest ou react-server-dom-turbopack@latest, selon le cas.
- Mettre à jour les dépendances des frameworks — Next.js, React Router, Waku et d'autres frameworks concernés ont publié les correctifs correspondants. Consultez l'avis de l'équipe React pour connaître les procédures de mise à jour spécifiques à chaque version.
- Ne vous fiez pas uniquement aux mesures prises par les hébergeurs: bien que des fournisseurs tels que Vercel aient déployé des règles WAF temporaires après la divulgation de la faille, il s'agit là de mesures provisoires qui ne remplacent pas l'application des correctifs aux paquets sous-jacents.
- Vérifiez les journaux du serveur à la recherche de requêtes POST comportant des en-têtes Next-Action et des corps de type multipart/form-data contenant les motifs $@ ou __proto__, et surveillez les journaux de l'application pour détecter d'éventuels appels inattendus à child_process ou execSync.
Mesures d'atténuation avec OPSWAT
OPSWAT , une technologie propriétaire intégrée à la plateforme MetaDefender™, offre la visibilité et le contrôle nécessaires pour se prémunir contre des vulnérabilités telles que CVE-2025-55182. Comme cette faille réside dans des paquets npm open source (react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack), les organisations doivent d'abord dresser un inventaire complet des emplacements où ces composants sont déployés au sein de leur infrastructure avant de pouvoir mettre en place une correction efficace.

OPSWAT génère un inventaire complet de tous les composants logiciels, bibliothèques, conteneurs et dépendances utilisés. Lors de l'analyse d'applications ou d'images de conteneurs contenant des paquets React vulnérables, le système signale automatiquement la faille CVE-2025-55182 comme « critique » et fournit des conseils sur les versions corrigées disponibles, ce qui permet aux équipes de sécurité de hiérarchiser les priorités et d'appliquer les correctifs avant que l'exploitation ne se produise.
OPSWAT est disponible à la fois dans MetaDefender — pour l'analyse des applications individuelles et des images de conteneurs — et dans MetaDefender Software Chain™ — pour une visibilité au niveau du pipeline sur l'ensemble de votre cycle de vie de développement. Ensemble, ces solutions permettent aux équipes de sécurité de :
- Identifiez rapidement les composants vulnérables: déterminez immédiatement quelles applications et quels conteneurs contiennent les paquets react-server-dom-* concernés dans des versions vulnérables, afin de vous assurer qu'aucun déploiement n'est oublié.
- Assurer une mise à jour proactive des correctifs - Surveiller en permanence les dépendances open source afin de détecter les paquets obsolètes ou non sécurisés dès la publication de nouveaux avis de sécurité, réduisant ainsi la durée d'exposition aux risques.
- Assurer la conformité et la transparence de la chaîne d'approvisionnement - Respecter les exigences réglementaires en conservant un registre vérifiable de tous les composants logiciels et de leurs vulnérabilités connues.
La rapidité et l'ampleur de l'exploitation de la faille CVE-2025-55182 – plus de 582 millions de tentatives rien que la première semaine – montrent clairement pourquoi se contenter d'appliquer des correctifs de manière réactive n'est plus suffisant. Savoir ce que l'on possède, où cela s'exécute et quand cela devient vulnérable constitue le fondement d'une défense proactive. Cette visibilité commence par la SBOM.
Références
- Équipe React - Vulnérabilité de sécurité critique dans Server React
- Wiz Research - React2Shell (CVE-2025-55182) : vulnérabilité critique dans React
- Trend Micro - CVE-2025-55182 : analyse de React2Shell, chaos autour du PoC et exploitation en milieu réel
- Akamai - CVE-2025-55182 : Exécution de code à distance (RCE) par désérialisation dans Server React et Next.js
- Google Cloud Plusieurs acteurs malveillants exploitent la vulnérabilité React2Shell (CVE-2025-55182)
- AWS - Des groupes de cybercriminels liés à la Chine exploitent rapidement React2Shell
- NVD - CVE-2025-55182
- Comprendre Server React - Tony Alicea
- MDN Web Docs - Opérateur `await`
- Code source React - ReactFlightReplyServer.js (v19.0.0)
- Code source Next.js - action-handler.ts (v16.0.0)
