La vulnérabilité RCE critique CVE-2025-11953 met en danger les développeurs React Native

Mise à jour du 9 février 2026 : clarification de la différence d’impact avant la version 17.0.0 et à partir de la version 17.0.0.

L’équipe de recherche en sécurité de JFrog a récemment découvert et divulgué la CVE-2025-11953, une vulnérabilité de sécurité critique (CVSS 9,8) affectant le package NPM @react-native-community/cli, extrêmement populaire et téléchargé environ 2 millions de fois par semaine.

Cette vulnérabilité permet à des pirates distants non authentifiés de déclencher facilement l’exécution de commandes arbitraires du système d’exploitation sur la machine exécutant le serveur de développement de react-native-community/cli, ce qui représente un risque important pour les développeurs.

React Native est un framework populaire permettant de créer des applications mobiles multiplateformes à l’aide de JavaScript. La vulnérabilité se trouve dans un package appartenant au projet plus vaste React Native Community CLI, qui a été séparé du code source principal de react-native il y a quelques années afin d’en faciliter la maintenance. Cette interface en ligne de commande est un ensemble d’outils qui aident les développeurs à créer des applications mobiles React Native. Elle est officiellement utilisée pour créer des applications mobiles React Native sans recourir à un framework, ainsi que des applications React Native pour Windows, React Native pour macOS, etc.

Contrairement aux vulnérabilités classiques des serveurs de développement, qui ne peuvent être exploitées que depuis la machine locale d’un développeur, un second problème de sécurité repéré par l’équipe dans le code source principal de React Native expose le serveur de développement aux attaques provenant de réseaux externes. Ce second problème rend la première vulnérabilité extrêmement critique.

Nous tenons à remercier l’équipe de sécurité et les ingénieurs de Meta pour leur prise en charge rapide de la vulnérabilité CVE-2025-11953.

Qui est affecté par la CVE-2025-11953 ?

Les développeurs qui ont initialisé leur projet React Native avec une version vulnérable de @react-native-community/cli et qui exécutent le serveur de développement Metro à l’aide de l’une des commandes suivantes ou de commandes similaires sont vulnérables à CVE-2025-11953 :

 npm start
 npm run [start|android|ios|windows|macos]
 npx react-native [start|run-android|run-ios|run-windows|run-macos]
 npx @react-native-community/cli [start|run-android|run-ios|run-windows|run-macos]

 Serveur de développement de react-native (Metro) en cours d’exécution

Bien que la vulnérabilité soit exploitable par défaut lors de l’initialisation d’un projet react-native à l’aide de @react-native-community/cli, il est important de comprendre que tous les développeurs ayant installé cette bibliothèque en tant que dépendance ne sont pas nécessairement vulnérables.

Plus précisément, les développeurs qui utilisent React Native avec un framework n’employant pas Metro comme serveur de développement ne sont généralement pas vulnérables.

La vulnérabilité affecte directement les versions 4.8.0 à 20.0.0-alpha.2 du package @react-native-community/cli-server-api. Elle est corrigée depuis la version 20.0.0.

Dans les versions 4.8.0 antérieures à la version 17.0.0, la vulnérabilité peut permettre à des pirates distants non authentifiés d’exécuter des fichiers exécutables déjà présents sur une machine distante (sans fournir le moindre argument). Cette possibilité peut être utile aux pirates capables de transférer, d’une manière ou d’une autre, des fichiers exécutables spécialement conçus vers la machine distante.

Cependant, à partir de la version 17.0.0 et jusqu’à la version 20.0.0-alpha.2, la vulnérabilité devient encore plus critique et peut conduire à l’exécution complète et non authentifiée de commandes distantes du système d’exploitation. Nous allons en présenter les détails ici.

Voici comment vérifier si le package vulnérable est présent dans un projet Node.js particulier :

 cd 
 npm list @react-native-community/cli-server-api

Le package peut également être installé globalement sur votre système. Vous pouvez le vérifier en exécutant :

 npm list -g @react-native-community/cli-server-api

Notez que le package concerné est généralement inclus avec @react-native-community/cli dans les versions correspondantes. Par conséquent, les projets utilisant les versions 4.8.0 à 20.0.0‑alpha.2 de @react-native-community/cli sont susceptibles d’inclure des versions vulnérables de @react-native-community/cli-server-api.

Sous Windows, nous avons démontré que cette vulnérabilité permet l’exécution de commandes arbitraires du système d’exploitation (notamment de commandes shell avec un contrôle total des paramètres). Sous macOS et Linux, la vulnérabilité permet l’exécution de fichiers exécutables arbitraires avec un contrôle limité des paramètres. Des recherches supplémentaires pourraient permettre d’obtenir l’exécution de commandes arbitraires du système d’exploitation sur ces plateformes.

Comment atténuer la CVE-2025-11953 ?

L’exécution des étapes suivantes permettra d’atténuer la CVE-2025-11953 :

  • Mettez à jour @react-native-community/cli-server-api vers la version 20.0.0 ou une version ultérieure, qui comprend un correctif pour cette vulnérabilité, dans chacun de vos projets react-native. Il s’agit de la solution recommandée.
  • Pour renforcer la sécurité, ou si la mise à niveau est impossible, liez explicitement le serveur de développement à l’interface localhost en ajoutant l’option « –host 127.0.0.1 », comme dans les exemples ci-dessous :
 npx react-native start --host 127.0.0.1
 npx @react-native-community/cli start --host 127.0.0.1

Résumé de la CVE-2025-11953

Le serveur de développement Metro, lancé par @react-native-community/cli, se lie par défaut aux interfaces externes (ce qui constitue déjà en soi un petit problème de sécurité). Le point de terminaison /open-url du serveur traite une requête POST comprenant une valeur saisie par l’utilisateur. Cette valeur est transmise à la fonction non sécurisée open(), fournie par le package NPM open, ce qui entraîne l’exécution d’une commande du système d’exploitation.

Un pirate non authentifié présent sur le réseau peut ainsi envoyer une requête POST au serveur et exécuter des commandes shell arbitraires avec des paramètres qu’il contrôle.

<code style=" width="400" height="364" />exécution de calc.exe à la suite de notre exploitation de la vulnérabilité

Détails techniques de la CVE-2025-11953

Lors de la création d’un projet React Native, les développeurs peuvent choisir d’utiliser un framework existant, comme Expo, qui emploie sa propre interface en ligne de commande et n’est pas vulnérable. Ils peuvent également créer leur application sans framework, comme nous le ferons ici, s’ils préfèrent développer leur propre framework ou s’ils ont certaines contraintes avec lesquelles un framework donné interfère.

Le processus de développement commence généralement par l’utilisation de @react-native-community/cli afin d’initialiser un nouveau projet à l’aide de la commande npx @react-native-community/cli init MyApp. Celle-ci met en place la structure, les dépendances et les fichiers de configuration nécessaires au projet. Pendant la phase de développement actif, les développeurs s’appuient sur l’interface en ligne de commande pour lancer le serveur de développement Metro (à l’aide de commandes telles que npx react-native start). Ce serveur transmet le bundle JavaScript à l’application en cours d’exécution (sur un émulateur ou un appareil mobile) et active des fonctionnalités de développement essentielles, telles que le rechargement à chaud et l’actualisation rapide, afin de permettre des itérations rapides.

Lors de l’exécution de npx react-native start, le fichier cli.js de react-native transmet la commande au package @react-native-community/cli (une dépendance facultative de react-native), qui exécute sa fonction build\index.js::setupAndRun().   Cette fonction charge les commandes de l’interface en ligne de commande (c’est-à-dire la commande start) en appelant loadConfigAsync, qui trouve et utilise le fichier node-modules/react-native/react-native.config.js pendant le processus.

 const commands = [];
 const {
   bundleCommand,
   startCommand,
 } = require('@react-native/community-cli-plugin');
 commands.push(bundleCommand, startCommand);

node-modules/react-native/react-native.config.js – ajoute startCommand à une liste de commandes exportée, qui est ensuite ajoutée à la liste des commandes de l’interface en ligne de commande.

Le fichier react-native.config.js importe startCommand depuis @react-native/community-cli-plugin, qui est chargé d’exécuter le serveur de développement.

 import runServer from './runServer';
 /* ... */
 const startCommand: Command = {
   name: 'start',
   func: runServer,
   description: 'Start the React Native development server.',

Extrait du fichier index.js de la commande start

Lorsque la commande start s’exécute – elle appelle runServer.js:

 /* ... */

 import {createDevServerMiddleware} from './middleware'; /* *** 1 *** */

 /* ... */

 /* *** 2 *** */
 const { middleware: communityMiddleware } = createDevServerMiddleware({
     host: hostname,
     port,
     watchFolders,
   });

 /* ... */

   /* *** 3 *** */
   await Metro.runServer(metroConfig, {
     host: args.host,
     secure: args.https,
     secureCert: args.cert,
     secureKey: args.key,
     unstable_extraMiddleware: [communityMiddleware, middleware],
     websocketEndpoints: {
       ...communityWebsocketEndpoints,
       ...websocketEndpoints,
     },
   });

Version simplifiée de runServer.js

runServer.js effectue les opérations suivantes :

  1. Le code importe la fonction createDevServerMiddleware depuis ./middleware, qui l’importe elle-même depuis le fichier index.ts de @react-native-community/cli-server-api.
  2. Il exécute createDevServerMiddleware, qui crée un middleware, c’est-à-dire une fonction ajoutée à un serveur HTTP qui intercepte les requêtes entrantes, ajoute des fonctionnalités, puis les transmet au gestionnaire suivant. Dans notre cas, le gestionnaire ajouté qui nous intéresse est /open-url, pris en charge par le gestionnaire openURLMiddleware. Le middleware est affecté à la variable communityMiddleware.
  3. Exécute le serveur Metro, avec communityMiddleware comme paramètre. Il s’agit du serveur de développement de la CLI.

Examinons maintenant le gestionnaire openURLMiddleware ajouté au serveur dans le cadre de communityMiddleware :

 async function openURLMiddleware(req: IncomingMessage, ...) {
   if (req.method === 'POST') {

     /* ... */

     const {url} = req.body as {url: string};

     await open(url);

     /* ... */

   }
   next();
 }
 export default connect().use(json()).use(openURLMiddleware);

Extrait simplifié de la fonction openURLMiddleware du fichier openURLMiddleware.ts

Cette fonction est appelée avec un paramètre req correspondant à une requête HTTP POST au format JSON, comprenant un champ de type chaîne nommé url. Elle récupère la valeur du champ url, qui est une chaîne non assainie provenant directement de l’utilisateur, et l’utilise comme paramètre de l’appel à open().

  1. La fonction open() est importée depuis la version 6.4.0 du package NPM « open ».
  2. open(url) effectue les opérations suivantes sur les machines Windows :
    1. Elle définit « cmd » comme commande à exécuter.
    2. Construit le tableau cliArguments : ['/c', 'start', '""', '/b']
    3. Elle échappe le caractère & en le faisant précéder de ^ dans la chaîne cible, qui correspond à l’URL que nous avons fournie.
    4. Ajoute la chaîne target à cliArguments
    5. Exécute la commande suivante :
      childProcess.spawn(command, cliArguments, childProcessOptions);

La commande cmd start peut accepter une seule URL comme paramètre et l’ouvrir dans le navigateur par défaut grâce à son schéma d’URI HTTP. Elle accepte également le format suivant pour exécuter des commandes :
start “title” /b command optional_command_parameters

Dans le cas de open(“calc.exe”), le nouveau processus suivant est créé :

 cmd /c start “” /b calc.exe

Ce qui entraîne l’exécution de calc.exe.

Pour exécuter n’importe quelle commande arbitraire, nous pourrions définir le paramètre url de open sur une commande cmd /c . Par exemple, cela entraînerait la création du processus suivant :

 cmd /c start "" /b cmd /c echo abc > c:\temp\pwned.txt

React Native CVE – image5React Native CVE - image5
Un nouveau fichier pwned.txt est créé dans C:\temp à la suite de notre commande cmd.exe /c, ce qui prouve que l’exécution de code arbitraire a réussi.

Surfaces d’attaque sur différents systèmes d’exploitation

Contrairement à Windows, comme indiqué ci-dessus, le package open utilise des chemins de code différents sous macOS et Linux, en exécutant respectivement open et xdg-open . Sur les systèmes d’exploitation de type Unix, le processus créé s’attend à ce que chaque argument soit une chaîne distincte dans cliArguments. Ce comportement diffère de celui de Windows, où le processus créé attend une seule chaîne d’argument lpCommandLine, constituée avant l’appel à CreateProcess.

En outre, les deux commandes sont exécutées sans shell. Combinés au fait que nous ne contrôlons qu’une seule chaîne (url), ces éléments ne permettent pas d’exécuter directement des commandes arbitraires sur macOS et Linux.

xdg-open et open déterminent tous deux si la chaîne fournie correspond à un fichier ou à un URI, puis agissent en conséquence.

La surface d’attaque permettant d’obtenir une exécution de code sur ces systèmes d’exploitation peut dépendre de la configuration du système et comprend généralement les possibilités suivantes :

  • Trouver un schéma d’URI dont le gestionnaire peut permettre de franchir une étape de l’exploitation (soit grâce à une fonctionnalité, soit en exploitant une vulnérabilité du gestionnaire lui-même).
  • Exécuter un fichier local à l’aide d’un schéma d’URI classique ou file://. (Éventuellement, combiner cette méthode à une primitive de dépôt de fichiers.)
  • Exécution d’un fichier distant sur le serveur de l’attaquant à l’aide des schémas d’URI smb://, dav:// ou similaires. (ce qui déclenchera probablement des boîtes de dialogue de sécurité demandant l’approbation de l’utilisateur victime)

En conclusion, l’exécution de commandes arbitraires du système d’exploitation sur ces plateformes pourrait être réalisable moyennant des recherches supplémentaires.

Le serveur de développement ne devrait-il pas se lier uniquement à localhost ?

Comme nous l’avons montré précédemment, lors de l’exécution du serveur de développement Metro, celui-ci indique explicitement qu’il démarre sur l’interface localhost :

React Native CVE - image4

Cependant, en réalité, nous pouvons constater que le serveur est à l’écoute sur toutes les interfaces ! (0.0.0.0 et IPv6 [::]) :

React Native CVE - image2

Comme indiqué précédemment, cela est dû à une autre vulnérabilité que nous avons découverte et divulguée dans @react-native/community-cli-plugin, et qui a été considérée comme « informative ».

Lors de l’exécution de npx react-native start, le code atteint la fonction runServer.js::runServer :

 async function runServer(
   _argv: Array,
   cliConfig: Config,
   args: StartCommandArgs,
 ) {

   /* ... */

   const hostname = args.host?.length ? args.host : 'localhost'; // *** 1 ***

   /* ... */

   const protocol = args.https === true ? 'https' : 'http';
   const devServerUrl = url.format({protocol, hostname, port}); // *** 2 ***

   /* ... */

   console.info(`Starting dev server on ${devServerUrl}\n`); // *** 3 ***

   /* ... */

   await Metro.runServer(metroConfig, {
     host: args.host, // *** 4 ***

Parties pertinentes de la fonction runServer du fichier runServer.js

Nous supposerons que la commande start est appelée avec les valeurs par défaut et sans paramètres facultatifs. Suivons chacune des étapes de l’exemple de code ci-dessus :

    1. hostname contient le paramètre host s’il a été fourni, ou « localhost » dans le cas contraire.
    2. devServerUrl est formée à partir de hostname.
    3. Le message trompeur “Starting dev server on http://localhost:8081” est affiché.
    4. La fonction Metro.runServer, chargée d’exécuter le serveur de développement Metro, est appelée avec args.host comme valeur du paramètre host. Cette valeur est undefined, alors que la variable hostname aurait dû être utilisée.

Plus loin, le code de la fonction runServer de Metro utilise ce paramètre undefined lors de l’appel à httpServer.listen.

La documentation de server.listen du module http de Node nous permet de comprendre que cette fonction se comporte comme server.listen de net.Server, qui indique :

« Si host est omis, le serveur acceptera les connexions sur l’adresse IPv6 non spécifiée (::) lorsque le protocole IPv6 est disponible, ou sur l’adresse IPv4 non spécifiée (0.0.0.0) dans le cas contraire. Sur la plupart des systèmes d’exploitation, l’écoute sur l’adresse IPv6 non spécifiée (::) peut également amener net.Server à écouter sur l’adresse IPv4 non spécifiée (0.0.0.0). »

Transmettre explicitement undefined revient fonctionnellement à omettre le paramètre. Par conséquent, le serveur accepte par défaut les connexions externes.

Les points de terminaison HTTP destinés uniquement au développement sont donc exposés aux pirates présents sur le réseau.

Résumé

Cette vulnérabilité montre que même des failles simples d’exécution de code à distance, telles que la transmission d’une saisie utilisateur au shell du système, existent encore dans des logiciels réels. C’est particulièrement le cas lorsque la fonction sensible dangereuse se trouve en réalité dans du code tiers, comme la fonction « open » importée dans cet exemple. Elle rappelle que les pratiques de programmation sécurisée et l’analyse de sécurité automatisée sont indispensables pour empêcher que ces failles faciles à exploiter n’atteignent l’environnement de production.

Un bon moyen d’éviter ces vulnérabilités et d’autres failles similaires dans votre propre code consiste à déployer l’analyse SAST de JFrog Advanced Security, qui permet aux développeurs de détecter et de corriger les problèmes de sécurité dès le début du processus de développement. Ne nécessitant aucune configuration, JFrog SAST s’intègre facilement aux workflows existants grâce à des extensions d’EDI et à des outils en ligne de commande. Les développeurs accèdent ainsi facilement à des résultats exploitables sans interrompre leur travail.

Par exemple, la capture d’écran ci-dessous montre comment JFrog SAST détecte la vulnérabilité décrite ci-dessus lorsqu’elle est analysée comme du code interne directement depuis Visual Studio Code :

JFrog SAST détecte la vulnérabilité et affiche ses détails ainsi que les preuves de traçage des données (Data Trace Evidence). (Cliquez sur l’image pour l’agrandir.)

JFrog SAST met en évidence la vulnérabilité dans le code, accompagnée de ses détails, et fournit des preuves de traçage des données montrant le cheminement des données depuis le champ req.body saisi par l’utilisateur jusqu’à son utilisation comme paramètre de la fonction dangereuse open.

Pour rester informé des autres attaques et vulnérabilités zero-day, consultez régulièrement le centre de recherche en sécurité de JFrog afin de découvrir les dernières informations concernant les CVE, les vulnérabilités et les correctifs.