La prototype pollution est l'une des vulnérabilités JavaScript les plus insidieuses et les plus sous-estimées de la dernière décennie. Contrairement aux injections SQL ou aux XSS classiques, elle exploite un mécanisme fondamental du langage lui-même : la chaîne de prototypes. En 2026, malgré des correctifs appliqués sur lodash, jQuery et des dizaines de bibliothèques majeures, cette classe de failles continue de surgir dans des applications en production, des pipelines CI/CD et des frameworks Node.js. Les conséquences vont du simple contournement de contrôle d'accès jusqu'à l'exécution de code à distance (RCE) côté serveur. Selon les données HackerOne, les rapports de prototype pollution ont généré plusieurs centaines de milliers de dollars en primes bug bounty entre 2019 et 2025. Ce guide technique vous explique, en détail, comment fonctionne l'exploitation de cette vulnérabilité côté client et côté serveur, quelles CVE ont marqué l'histoire de la sécurité JavaScript, quels outils permettent de la détecter, et surtout comment s'en défendre durablement dans vos applications.

Comprendre les prototypes JavaScript : la base de tout

Pour saisir la prototype pollution, il est indispensable de maîtriser le concept de prototype en JavaScript. Contrairement aux langages orientés objet classiques comme Java ou C# qui reposent sur un modèle de classes strictement hiérarchiques, JavaScript utilise un système d'héritage par prototype. Chaque objet possède une propriété interne, notée [[Prototype]], qui pointe vers un autre objet dont il hérite les propriétés et méthodes. Cette chaîne remonte jusqu'à Object.prototype, le sommet de la hiérarchie, qui contient les méthodes universelles comme hasOwnProperty(), toString(), valueOf() ou isPrototypeOf().

Lorsque vous écrivez const obj = {}, JavaScript crée un objet ordinaire dont le prototype est Object.prototype. Si vous accédez à obj.toString(), le moteur JavaScript ne trouve pas toString dans les propriétés directes de obj, il remonte la chaîne et le trouve dans Object.prototype. Ce mécanisme de délégation est la pierre angulaire du langage.

La propriété __proto__ (bien que dépréciée dans les spécifications modernes au profit de Object.getPrototypeOf() et Object.setPrototypeOf()) reste accessible dans la grande majorité des moteurs JavaScript pour des raisons de compatibilité ascendante. C'est précisément cette rétrocompatibilité qui crée la surface d'attaque. Lorsqu'un développeur manipule des objets JSON issus d'une entrée utilisateur sans validation, un attaquant peut manipuler cette chaîne de prototypes pour injecter des propriétés sur Object.prototype lui-même, affectant alors tous les objets de l'application créés après la pollution.

Il est important de distinguer trois vecteurs principaux d'accès au prototype d'un objet dans un contexte d'attaque : la propriété __proto__ (raccourci legacy), la propriété constructor.prototype (accès via le constructeur), et la référence directe Object.prototype. Ces trois chemins mènent au même résultat : modifier le prototype partagé par tous les objets de l'application. C'est ce que l'on appelle la pollution de prototype, ou prototype pollution.

La popularité de JavaScript — langage numéro un selon l'indice TIOBE et l'enquête Stack Overflow depuis plus de dix ans consécutifs — fait de cette vulnérabilité un enjeu de sécurité global. Des applications mobiles hybrides aux APIs REST d'entreprise, en passant par les microservices serverless, tout l'écosystème JavaScript est potentiellement exposé si les développeurs ne comprennent pas les mécanismes de la chaîne de prototypes et les risques qu'ils engendrent lorsque des données d'entrée non validées sont traitées.

Le principe de la vulnérabilité : comment polluer un prototype

La prototype pollution survient lorsqu'un attaquant peut contrôler les clés et valeurs d'un objet lors d'une opération de fusion récursive (deep merge), de clonage profond, ou de parsing de données structurées. Le scénario type est le suivant : une bibliothèque utilitaire reçoit un objet JSON contrôlé par l'attaquant et le fusionne récursivement avec un objet cible sans valider les clés spéciales.

Voici un exemple minimal pour illustrer le mécanisme :

// Fonction de merge récursif naïve (vulnérable)
function merge(target, source) {
  for (let key in source) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      if (!target[key]) target[key] = {};
      merge(target[key], source[key]);
    } else {
      // Ici, si key === '__proto__' et source[key] = { isAdmin: true }
      // on écrit directement sur Object.prototype
      target[key] = source[key];
    }
  }
  return target;
}

const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, payload);

// Maintenant TOUS les objets héritent de isAdmin: true
const user = {};
console.log(user.isAdmin); // true — prototype pollué !

La pollution a un effet global et persistant pendant toute la durée de vie du processus (ou de la page dans un navigateur). Une fois Object.prototype.isAdmin défini à true, chaque nouvel objet créé avec {}, new Object() ou tout littéral objet héritera automatiquement de cette propriété, sauf s'il a été créé avec Object.create(null). C'est cette globalité qui rend la vulnérabilité particulièrement dangereuse.

Le vecteur constructor.prototype fonctionne de façon identique :

const payload = JSON.parse('{"constructor": {"prototype": {"isAdmin": true}}}');
merge({}, payload);
// Même résultat : Object.prototype.isAdmin === true

Ces deux vecteurs sont les plus fréquemment exploités dans les CVE documentées. Un bon système de détection doit surveiller les deux chemins d'accès au prototype. Il convient également de noter que dans les environnements utilisant des parsers JSON stricts ou des moteurs V8 récents, certaines protections peuvent limiter l'impact, mais elles ne constituent pas une défense suffisante à elles seules.

La dangerosité de cette vulnérabilité réside aussi dans son caractère silencieux : contrairement à une injection SQL qui peut déclencher une erreur visible, la pollution de prototype se produit sans aucun log ni exception dans la plupart des cas. L'application continue de fonctionner normalement, mais son comportement est subtilement altéré, parfois des heures ou des jours avant qu'une exploitation concrète soit tentée.

Types d'exploitation : client-side et server-side

La prototype pollution peut être exploitée dans deux contextes distincts avec des impacts différents selon l'environnement d'exécution.

Exploitation côté client (navigateur) : dans un contexte front-end, l'attaquant cherche à injecter une charge utile via un vecteur contrôlable depuis l'extérieur. Les vecteurs courants incluent les paramètres d'URL parsés par des bibliothèques comme qs ou jquery-deparam, les formulaires envoyant des objets JSON imbriqués, ou encore les messages postMessage entre iframes. Par exemple, une URL du type https://app.example.com/page?__proto__[isAdmin]=true peut polluer le prototype si le parser de query string fusionne naïvement les paramètres dans un objet. La bibliothèque qs dans ses versions anciennes était vulnérable à ce type d'injection. L'impact côté client inclut généralement le contournement de vérifications de permission, l'injection de contenu HTML via des templates pollués (menant à du DOM XSS), ou la manipulation du comportement de l'application pour faciliter d'autres attaques.

Un vecteur particulièrement répandu côté navigateur est l'exploitation via des données stockées en localStorage ou sessionStorage. Si une application sauvegarde des objets JSON sérialisés dans le stockage local et les désérialise sans validation au chargement, un attaquant ayant exécuté du code JavaScript dans le contexte de la page (via XSS préalable) peut polluer le stockage et déclencher la pollution à chaque visite ultérieure de l'utilisateur, créant un vecteur de persistance.

Exploitation côté serveur (Node.js) : c'est ici que les impacts deviennent critiques. Dans une application Express.js recevant des requêtes JSON, un attaquant peut envoyer un corps de requête contenant la clé __proto__ :

POST /api/merge-settings HTTP/1.1
Content-Type: application/json

{
  "__proto__": {
    "polluted": "value",
    "isAdmin": true
  }
}

Si le handler Express fusionne ce corps avec un objet interne sans protection, tous les objets créés ultérieurement dans le même processus Node.js hériteront de ces propriétés. Le processus Node.js est partagé entre toutes les requêtes, contrairement à PHP qui recrée un contexte par requête. La pollution est donc globale pour l'ensemble de l'application en cours d'exécution. C'est une distinction fondamentale que les développeurs Node.js doivent absolument intégrer dans leur modèle de menace.

L'exploitation serveur est amplifiée par l'architecture event-loop de Node.js : un seul processus gère des milliers de requêtes concurrentes. Une pollution réussie sur une requête d'un utilisateur non authentifié peut immédiatement affecter les requêtes concurrentes d'administrateurs, créant des fenêtres d'exploitation temporelles que les logs traditionnels ne permettent pas toujours d'identifier.

Impact de la prototype pollution

Les conséquences d'une exploitation réussie de prototype pollution varient considérablement selon l'environnement et la logique applicative, mais peuvent atteindre le niveau de criticité maximale.

Contournement de contrôle d'accès : c'est l'impact le plus immédiat et le plus fréquent. Si une route Express effectue une vérification du type if (req.user.isAdmin) {...} et que le prototype a été pollué avec isAdmin: true, la vérification retourne true pour n'importe quel utilisateur, même non authentifié. Le même principe s'applique aux propriétés comme isAuthenticated, role, permissions, etc.

Injection de propriétés inattendues : un attaquant peut injecter des propriétés qui modifient le comportement de bibliothèques tierces. Par exemple, polluer la propriété outputFunctionName dans le contexte d'EJS, ou lineComment dans Pug, peut mener à une injection de code dans le processus de rendu des templates. Ces gadgets dans les moteurs de templates sont particulièrement dangereux car ils transforment une primitive de pollution "modérée" en vecteur d'exécution de code.

Exécution de code à distance (RCE) : dans Node.js, certaines combinaisons de pollution et de "gadgets" dans le code de l'application permettent d'atteindre la RCE. La technique la plus connue implique la pollution de propriétés utilisées par child_process.spawn(), comme shell ou env. Si une propriété comme NODE_OPTIONS est injectée via le prototype d'un objet transmis à spawn(), un attaquant peut forcer l'exécution de code arbitraire.

Déni de service (DoS) : polluer des propriétés utilisées par des algorithmes de tri, de comparaison ou de sérialisation peut provoquer des boucles infinies ou des erreurs fatales, rendant l'application indisponible. CVE-2022-24785 (moment.js) et d'autres CVE montrent que même des bibliothèques de traitement de dates peuvent être des vecteurs de DoS via prototype pollution.

Exfiltration de données : dans certaines architectures où les objets utilisateur sont sérialisés et renvoyés dans les réponses API, une pollution réussie peut insérer des propriétés dans la sérialisation, révélant des informations internes du système ou des données d'autres utilisateurs.

CVE emblématiques : lodash, jQuery, immer et Axios

La prototype pollution a touché certaines des bibliothèques JavaScript les plus utilisées au monde, illustrant à quel point cette classe de vulnérabilité est répandue dans l'écosystème.

CVE-2019-10744 — lodash < 4.17.13 : lodash est l'une des bibliothèques JavaScript les plus téléchargées (plusieurs milliards de téléchargements npm). La fonction _.defaultsDeep() effectuait une fusion récursive profonde sans filtrer les clés __proto__ et constructor. Un attaquant pouvant contrôler l'argument source de _.defaultsDeep() pouvait polluer Object.prototype. La fonction _.merge() et _.set() étaient également affectées dans certaines versions. Le score CVSS était de 9.8 (Critique). Le correctif consistait à vérifier explicitement les clés problématiques avant toute assignation récursive. La version corrigée est 4.17.13 ; la mise à jour recommandée est 4.17.21 qui inclut des correctifs supplémentaires découverts après la divulgation initiale.

CVE-2019-11358 — jQuery < 3.4.0 : jQuery, utilisé dans des centaines de millions de sites web, était vulnérable via sa fonction $.extend() en mode deep copy. L'appel $.extend(true, {}, JSON.parse('{"__proto__": {"polluted": true}}')) polluait Object.prototype. Cette vulnérabilité affectait également de nombreux plugins jQuery qui utilisaient $.extend() pour fusionner leurs options de configuration avec les valeurs par défaut. Le score CVSS était de 6.1 (Moyen), mais l'impact réel dépendait fortement du contexte applicatif. La version corrigée est jQuery 3.4.0. Des millions d'applications utilisant jQuery dans des versions legacy restent potentiellement exposées aujourd'hui, notamment dans les systèmes CMS non mis à jour.

CVE-2020-28477 — immer < 8.0.1 : immer est une bibliothèque populaire pour la gestion de l'état immuable, largement utilisée dans les projets React/Redux. La vulnérabilité résidait dans la fonction produce() qui permettait d'appliquer des mutations à un "draft" d'objet sans filtrer les clés prototype. Un attaquant pouvant contrôler la structure des données d'entrée pouvait déclencher la pollution via des recettes de production immer. Le score CVSS était de 9.8 (Critique). La correction dans la version 8.0.1 introduisait une validation stricte des clés, bloquant les tentatives d'accès à __proto__, prototype et constructor.

Axios — vulnérabilités historiques : axios, le client HTTP le plus utilisé pour Node.js et les navigateurs, a connu plusieurs problèmes liés à la manipulation d'en-têtes et de configurations qui pouvaient indirectement faciliter des attaques de prototype pollution dans certains contextes. Bien que les vulnérabilités directes de prototype pollution dans le core axios aient été limitées, son pattern de merge de configuration (baseURL, headers, etc.) a été identifié comme un vecteur potentiel dans des audits de sécurité. Les versions récentes (>= 1.6.0) incluent des protections renforcées contre ces scénarios, notamment une validation des propriétés lors de la fusion des configurations de requête.

Ces CVE illustrent une réalité de l'écosystème npm : les fonctions utilitaires de fusion d'objets, omniprésentes dans les bibliothèques, ont systématiquement sous-estimé le risque posé par des clés JSON spéciales. La leçon à retenir est qu'il ne faut jamais faire confiance à une dépendance tierce pour sécuriser la fusion de données d'entrée utilisateur, même si cette dépendance est populaire et activement maintenue.

Outils de détection et d'audit

La détection de la prototype pollution nécessite une approche multicouche combinant tests automatisés, outils spécialisés et revue manuelle.

Test manuel basique : la première approche consiste à envoyer des payloads de test sur toutes les routes acceptant du JSON et à vérifier si la pollution prend effet. On peut utiliser curl pour tester une route :

curl -X POST https://app.example.com/api/endpoint \
  -H "Content-Type: application/json" \
  -d '{"__proto__": {"testPolluted": "pwned"}}'

# Vérification : observer si le comportement de l'application change
# ou si la propriété testPolluted apparaît dans les réponses JSON

Pour le test côté client, on peut utiliser la console du navigateur pour injecter manuellement et vérifier : ({}).testPolluted === 'pwned' doit retourner true si la pollution a réussi. Cette approche simple est souvent suffisante pour une détection initiale.

Burp Suite avec l'extension PP Scanner : l'extension Burp "Server-Side Prototype Pollution Scanner" automatise la découverte de ces vulnérabilités dans les applications web. Elle intercepte les requêtes HTTP, injecte des payloads de pollution de prototype dans les corps JSON et les paramètres, et analyse les réponses pour détecter les signes de pollution réussie. L'extension est particulièrement efficace pour les tests de pentest en boîte grise où l'on dispose d'un accès authentifié à l'application. Pour les consultants en sécurité, c'est l'outil de référence pour intégrer la détection de prototype pollution dans un workflow d'audit applicatif. L'extension utilise des techniques de détection indirecte (basées sur des variations de comportement) pour éviter de provoquer des effets secondaires indésirables sur l'application testée.

PPMap : PPMap est un outil open source dédié au scanning de prototype pollution, développé par la communauté bug bounty. Il explore automatiquement les propriétés des objets pollués pour identifier des "gadgets" exploitables dans le code de l'application. PPMap peut être utilisé en mode manuel (fourniture de la payload et observation) ou en mode automatique (scanning d'un ensemble d'URLs). Il est particulièrement utile pour passer de la découverte d'une primitive de pollution à la démonstration d'un impact concret (XSS, RCE). Son approche de cartographie des gadgets disponibles dans l'environnement d'exécution est précieuse pour évaluer la criticité réelle d'une vulnérabilité.

Snyk et npm audit : pour la détection des dépendances vulnérables, npm audit --production reste le premier réflexe. Il interroge la base de données des advisories npm et signale les packages connus pour être vulnérables à la prototype pollution avec leurs recommandations de mise à jour. Snyk offre une analyse plus profonde avec un graphe de dépendances et des correctifs automatisés via PR. Ces outils s'intègrent dans les pipelines CI/CD pour bloquer les déploiements de code contenant des dépendances vulnérables. Un scanner de vulnérabilités comme Nessus et Greenbone peut compléter l'analyse au niveau infrastructure pour identifier des versions de packages vulnérables déployées sur les serveurs de production.

Analyse statique (SAST) : des outils comme Semgrep disposent de règles spécifiques pour détecter les patterns de merge récursif sans validation des clés. La règle cherche les patterns d'accès à des propriétés avec des clés dynamiques dans des boucles récursives, et les appels à des fonctions de merge connues avec des données non validées. CodeQL (GitHub Advanced Security) permet également d'écrire des requêtes personnalisées pour détecter ces flux de données dangereux (taint analysis). L'intégration de ces outils SAST dans le pipeline CI/CD permet une détection précoce, avant même la phase de test.

Exploitation avancée : gadgets et techniques sophistiquées

La découverte d'une primitive de prototype pollution n'est que la première étape. Pour démontrer un impact concret, un attaquant (ou un pentesteur) doit identifier des "gadgets" dans le code de l'application.

Qu'est-ce qu'un gadget ? : un gadget de prototype pollution est un fragment de code existant dans l'application (ou ses dépendances) qui lit une propriété depuis un objet potentiellement pollué et l'utilise d'une manière qui produit un impact de sécurité. Les gadgets les plus précieux sont ceux qui mènent à XSS, RCE ou contournement d'authentification. L'identification des gadgets requiert une connaissance approfondie des bibliothèques utilisées par l'application et de leur comportement interne.

Client-side XSS via template engines : les moteurs de templates JavaScript comme Handlebars, Pug (Jade) et EJS sont des mines de gadgets. Dans certaines versions de Handlebars, la propriété __defineGetter__ pouvait être polluée pour déclencher l'exécution de code arbitraire lors du rendu de templates. Dans Pug, la propriété pretty ou self injectée via le prototype pouvait modifier le comportement de compilation des templates, potentiellement conduisant à du XSS stocké persistant. Ces gadgets sont particulièrement dangereux dans les applications qui utilisent des templates côté client pour le rendu dynamique de contenu.

Server-side RCE via child_process : la technique la plus critique pour Node.js implique la pollution de propriétés utilisées par child_process.spawn() ou child_process.fork(). Si l'application utilise une de ces fonctions avec un objet d'options construit à partir d'un objet héritant d'un prototype pollué, un attaquant peut injecter la propriété shell: true (forçant l'exécution via un shell système) ou manipuler env pour injecter des variables d'environnement comme NODE_OPTIONS=--require /tmp/malicious.js. La combinaison de ces techniques avec une primitive de pollution côté serveur peut mener à une RCE complète sans aucune interaction utilisateur supplémentaire.

AST injection avec lodash template : lodash expose une fonction _.template() qui compile des templates JavaScript. Certaines versions vulnérables permettaient de polluer la propriété sourceURL ou des options de compilation, menant à une injection dans l'AST (Abstract Syntax Tree) du template compilé. Cela permettait l'exécution de code JavaScript arbitraire dans le contexte du serveur Node.js. Cette technique, documentée par plusieurs chercheurs en sécurité, illustre comment une primitive de pollution "simple" peut être transformée en RCE complète via des gadgets dans des bibliothèques populaires.

Pollution via paramètres URL : la bibliothèque qs est le parser de query string par défaut dans Express.js via le middleware body-parser. Dans ses versions vulnérables, une URL comme ?__proto__[admin]=true était parsée en objet {"__proto__": {"admin": true}} qui, fusionné avec un objet existant, polluait le prototype. Express.js a depuis introduit des protections, mais les applications qui configurent leur propre parsing de query string ou utilisent des versions anciennes de qs restent potentiellement exposées à ce vecteur.

Défenses et remédiations : comment se protéger

La bonne nouvelle est que la prototype pollution est parfaitement défendable avec les bonnes pratiques. Plusieurs mesures complémentaires existent, de la correction immédiate à la défense en profondeur.

Object.freeze(Object.prototype) : la mesure la plus radicale et la plus efficace est de geler le prototype au démarrage de l'application. En ajoutant Object.freeze(Object.prototype); en tête du fichier d'entrée de votre application Node.js, toute tentative de modification de Object.prototype échouera silencieusement (ou avec une erreur en mode strict). Cette approche bloque toutes les formes de pollution de prototype. L'inconvénient potentiel est qu'elle peut casser des bibliothèques tierces mal conçues qui tentent d'ajouter des méthodes à Object.prototype (une mauvaise pratique en soi). Des tests approfondis après l'application de cette mesure sont nécessaires, mais dans la grande majorité des cas modernes, cette modification ne crée aucun problème.

Filtrage des clés dangereuses : dans toute fonction de merge récursif maison, valider les clés avant assignation :

function safeMerge(target, source) {
  const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
  for (let key in source) {
    if (dangerousKeys.includes(key)) continue; // bloquer les clés dangereuses
    if (typeof source[key] === 'object' && source[key] !== null) {
      if (!target[key]) target[key] = {};
      safeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

Cette approche par liste noire est moins robuste que Object.freeze() car de nouveaux vecteurs peuvent émerger, mais elle corrige les vecteurs connus documentés dans les CVE actuelles.

Object.create(null) : pour les objets utilisés comme stores de données (maps, caches, registres), utiliser Object.create(null) plutôt que {}. Ces objets n'ont pas de chaîne de prototypes et ne peuvent donc pas être contaminés par une pollution de Object.prototype. C'est une pratique recommandée pour tous les objets qui servent de dictionnaires clé-valeur dans une application.

// Vulnérable : hérite de Object.prototype
const store = {};

// Sûr : aucun prototype, immunisé contre la pollution
const safeStore = Object.create(null);

Mise à jour des dépendances : la mesure prioritaire reste la mise à jour des bibliothèques vulnérables. Les versions minimales recommandées sont lodash >= 4.17.21, jQuery >= 3.5.0, et immer >= 8.0.1. Un audit régulier avec npm audit --production doit être intégré dans le pipeline CI/CD comme gate de déploiement bloquant. Les alertes de sécurité Dependabot sur GitHub offrent une automatisation précieuse pour ce suivi continu.

JSON Schema validation : valider la structure des données JSON reçues avant tout traitement via un schéma JSON (ajv, joi, yup) permet de rejeter les payloads contenant des clés inattendues comme __proto__ ou constructor. Cette validation à la frontière de l'application réduit drastiquement la surface d'attaque et représente une bonne pratique de défense en profondeur. Un SIEM correctement configuré avec des règles de corrélation sur les logs applicatifs peut identifier des tentatives de pollution via des patterns de clés JSON anormaux et déclencher des alertes en temps réel pour l'équipe SOC.

Bonnes pratiques de développement sécurisé

La protection contre la prototype pollution s'inscrit dans une démarche globale de sécurité applicative que chaque équipe de développement devrait adopter.

Revue de code ciblée : lors de chaque revue de code, porter une attention particulière aux fonctions qui acceptent des objets JSON d'entrée utilisateur et les fusionnent avec des objets internes. Les patterns à surveiller incluent toute utilisation de for...in avec assignation récursive, les appels à Object.assign() avec des sources non validées, et les appels aux fonctions de merge de bibliothèques tierces avec des données d'entrée non sanitisées. Des checklists de sécurité spécifiques à la prototype pollution devraient faire partie du processus de revue pour chaque merge request introduisant du code de fusion d'objets.

Analyse statique (SAST) : intégrer des outils SAST dans le pipeline CI/CD permet de détecter automatiquement les patterns de code vulnérables avant le déploiement. Semgrep avec ses règles communautaires de sécurité JavaScript détecte de nombreux patterns de prototype pollution. ESLint avec le plugin eslint-plugin-security peut également signaler certains patterns à risque. Ces outils ne remplacent pas une revue humaine experte, mais ils constituent un premier filtre automatique précieux qui permet aux développeurs de se concentrer sur les aspects les plus complexes lors des revues manuelles.

Software Composition Analysis (SCA) : au-delà de l'analyse du code source propre, le SCA examine les dépendances transitives pour détecter les packages vulnérables. Des outils comme Snyk, Mend (anciennement WhiteSource), ou OWASP Dependency-Check maintiennent des bases de données à jour des vulnérabilités connues. L'intégration SCA dans le pipeline CI/CD avec un seuil de sévérité bloquant est une pratique essentielle pour les équipes qui pratiquent le DevSecOps. Des outils réseau comme Bettercap et Ettercap sont utiles pour l'analyse des flux réseau en complément des tests applicatifs lors des audits de sécurité complets, notamment pour vérifier l'exposition des endpoints JSON vulnérables sur le réseau interne.

Formation des développeurs : la sensibilisation est le premier rempart. Les développeurs JavaScript doivent comprendre le modèle de prototypes, les risques associés aux fonctions de merge récursif, et les bonnes pratiques de validation des entrées. Des formations spécifiques sur les vulnérabilités OWASP (la prototype pollution s'inscrit dans la catégorie A03:2021 — Injection) et des exercices pratiques sur des environnements de labs permettent d'ancrer ces connaissances de manière opérationnelle. Des sessions de "Capture The Flag" (CTF) internes centrées sur des scénarios de prototype pollution sont particulièrement efficaces pour sensibiliser les équipes de développement.

Tests de régression de sécurité : après la correction d'une vulnérabilité de prototype pollution, écrire des tests unitaires qui vérifient que les payloads malveillantes ne polluent pas le prototype. Ces tests doivent être maintenus dans la suite de tests de régression de sécurité pour s'assurer qu'une refactorisation future ne réintroduit pas la vulnérabilité. L'utilisation de frameworks de test comme Jest avec des assertions spécifiques sur l'état de Object.prototype avant et après l'exécution des fonctions de merge est recommandée.

Cas réels et retours d'expérience bug bounty

La prototype pollution a généré des découvertes majeures dans les programmes de bug bounty des plus grandes organisations. Ces cas réels illustrent concrètement l'impact de cette classe de vulnérabilités en production et les leçons que l'industrie en a tirées.

Découvertes sur des plateformes SaaS majeures : des chercheurs en sécurité ont identifié des vulnérabilités de prototype pollution dans des applications SaaS utilisées par des millions d'entreprises. Dans un cas documenté dans la littérature bug bounty, un chercheur a découvert qu'un endpoint API de configuration d'une grande plateforme de collaboration acceptait des objets JSON imbriqués arbitraires et les fusionnait directement avec les paramètres utilisateur côté serveur. En injectant {"__proto__": {"isEnterpriseUser": true}}, il a pu accéder à des fonctionnalités réservées aux abonnements enterprise sans payer. Ce type de vulnérabilité, lorsqu'il est découvert via un programme bug bounty officiel, peut valoir des primes dépassant 10 000 dollars selon la plateforme et l'impact démontré.

Impact en production chez des e-commerçants : lors d'audits applicatifs sur des plateformes e-commerce utilisant Node.js, des consultants ont découvert que des configurations de panier d'achat pouvaient être manipulées via prototype pollution pour injecter des propriétés comme discount ou isFreeShipping. Dans un cas concret, une propriété price injectée via le prototype affectait le calcul du total de la commande dans un contexte où l'application ne revalidait pas les prix côté serveur avant le traitement du paiement — une double vulnérabilité (prototype pollution + absence de revalidation serveur) menant à une fraude financière directe.

Escalade vers RCE dans un pipeline CI/CD : une vulnérabilité particulièrement sévère a été documentée dans un outil de build populaire basé sur Node.js. L'outil acceptait des fichiers de configuration JSON définis par l'utilisateur et les fusionnait avec sa configuration interne. En créant un fichier de configuration malveillant avec une clé __proto__ contenant des propriétés ciblant les options de child_process.spawn() utilisées en interne par l'outil, un attaquant ayant accès au dépôt de code pouvait déclencher l'exécution de commandes arbitraires lors du build. Dans un contexte de supply chain attack, où un acteur malveillant compromet une dépendance partagée, ce type de vulnérabilité représente un risque systémique pour toute l'industrie.

Leçons apprises de l'industrie : les retours d'expérience convergent vers plusieurs enseignements clés. Premièrement, la validation des entrées doit se faire à la frontière de l'application, avant tout traitement. Deuxièmement, le principe du moindre privilège s'applique aux données : ne jamais fusionner des données utilisateur directement avec des objets de configuration ou de session sans validation stricte de la structure. Troisièmement, les bibliothèques tierces doivent être considérées comme des surfaces d'attaque à surveiller activement. Quatrièmement, même une fois les CVE connues corrigées, des vecteurs spécifiques à l'application peuvent subsister si le code maison implémente des patterns de merge dangereux. Enfin, les tests de pénétration applicatifs réguliers — intégrés dans un cycle DevSecOps mature — doivent systématiquement inclure des tests de prototype pollution dans leur méthodologie pour détecter ces vulnérabilités avant que des acteurs malveillants ne le fassent.

À retenir

  • La prototype pollution exploite la chaîne de prototypes JavaScript pour injecter des propriétés malveillantes sur Object.prototype, affectant tous les objets de l'application — la défense la plus robuste est Object.freeze(Object.prototype) au démarrage.
  • Les CVE majeures (CVE-2019-10744 lodash, CVE-2019-11358 jQuery, CVE-2020-28477 immer) prouvent que même les bibliothèques les plus populaires ont été vulnérables — maintenir ses dépendances à jour via npm audit et Snyk est indispensable.
  • L'impact peut aller du contournement d'authentification jusqu'à la RCE complète côté Node.js via des gadgets dans les template engines (EJS, Pug) ou les appels child_process.spawn().
  • Utiliser Object.create(null) pour tous les objets servant de stores de données élimine leur exposition à la pollution, car ils n'ont pas de chaîne de prototypes.
  • La détection combine Burp Suite PP Scanner pour les tests manuels, PPMap pour l'identification de gadgets, npm audit/Snyk pour les dépendances, et Semgrep/CodeQL pour l'analyse statique dans le pipeline CI/CD.

Questions fréquentes sur la prototype pollution

La prototype pollution affecte-t-elle uniquement Node.js ou aussi les navigateurs ?

La prototype pollution affecte les deux environnements d'exécution JavaScript. Côté navigateur, elle peut conduire à du DOM XSS via des gadgets dans les moteurs de templates ou des bibliothèques de manipulation du DOM. Côté Node.js, les impacts sont généralement plus sévères (RCE possible) car le processus est partagé entre toutes les requêtes et l'environnement serveur offre plus de gadgets dangereux (child_process, système de fichiers). Les deux contextes nécessitent une attention spécifique lors des audits de sécurité applicatifs et doivent être couverts par les procédures de test de pénétration.

Comment savoir si mon application Node.js est actuellement vulnérable à la prototype pollution ?

Commencez par exécuter npm audit --production pour identifier les dépendances avec des CVE connues de prototype pollution. Ensuite, testez manuellement vos endpoints JSON en envoyant des payloads {"__proto__": {"testKey": "testValue"}} et vérifiez si ({}).testKey === 'testValue' retourne true après la requête. Intégrez également un outil SAST comme Semgrep avec les règles JavaScript de sécurité pour analyser votre code source. Un pentest applicatif professionnel reste la méthode la plus exhaustive pour évaluer l'exposition réelle et identifier les gadgets spécifiques à votre application.

Object.freeze(Object.prototype) peut-il casser mon application en production ?

Dans la grande majorité des applications modernes respectant les bonnes pratiques, Object.freeze(Object.prototype) n'introduit aucun problème. Les bibliothèques bien conçues n'ajoutent pas de méthodes à Object.prototype directement. En mode strict JavaScript, toute tentative de modification du prototype gelé lève une TypeError ; sans mode strict, elle échoue silencieusement. Il est recommandé de tester cette modification dans un environnement de staging complet avant de la déployer en production, et de surveiller les erreurs dans les logs applicatifs les premiers jours après déploiement pour détecter d'éventuelles incompatibilités avec des dépendances legacy.

Quelle est la différence entre prototype pollution et injection de dépendances ?

La prototype pollution est une vulnérabilité de sécurité qui exploite la chaîne de prototypes JavaScript pour injecter des propriétés malveillantes de manière globale, affectant tous les objets d'une application. L'injection de dépendances est un patron de conception légitime qui consiste à fournir les dépendances d'un composant de l'extérieur plutôt que de les instancier en interne — c'est une bonne pratique d'architecture logicielle. Les deux concepts partagent le mot "injection" mais n'ont aucun rapport en termes de sécurité : l'un est une vulnérabilité à corriger, l'autre est un patron à adopter.

Besoin d'un audit de sécurité applicatif ?

Ayi NEDJIMI accompagne les équipes développement avec des audits de sécurité applicatifs et des tests d'intrusion web. Détection de prototype pollution, analyse de dépendances vulnérables, revue de code sécurisé : bénéficiez d'une expertise reconnue pour sécuriser vos applications JavaScript et Node.js.

Demander un devis gratuit