Security
9 min read
2754 views

Pollution de Prototype : La vulnérabilité JavaScript qui empoisonne votre application ☣️

IT
InstaTunnel Team
Published by our engineering team
Pollution de Prototype : La vulnérabilité JavaScript qui empoisonne votre application ☣️

Dans le domaine de la sécurité des applications web, certaines vulnérabilités se manifestent par des crashs spectaculaires ou des violations évidentes. D’autres agissent silencieusement, corrompant votre application dès ses fondations. La pollution de prototype appartient à cette seconde catégorie, une vulnérabilité subtile mais dévastatrice qui exploite l’héritage prototypal de JavaScript pour empoisonner chaque objet de votre application avec une seule entrée malveillante.

Comprendre la chaîne de prototypes en JavaScript

Avant d’aborder l’attaque, il est essentiel de comprendre comment fonctionne l’héritage basé sur les prototypes en JavaScript. Contrairement aux langages orientés objet classiques, JavaScript utilise des prototypes pour partager propriétés et méthodes entre objets. Chaque objet JavaScript possède un lien interne vers un autre objet appelé son prototype, formant ce qu’on appelle la chaîne de prototypes.

Lorsque vous accédez à une propriété sur un objet, JavaScript vérifie d’abord si cette propriété existe directement sur l’objet. Si ce n’est pas le cas, il remonte la chaîne de prototypes, vérifiant chaque prototype jusqu’à ce qu’il trouve la propriété ou atteigne la fin de la chaîne à Object.prototype. Ce mécanisme élégant permet un partage efficace des propriétés mais crée aussi une surface d’attaque dangereuse.

Considérez ce code apparemment innocent :

const user = { name: 'Alice' };
console.log(user.toString); // [Function: toString]

Même si nous n’avons jamais défini de propriété toString sur l’objet user, JavaScript la trouve en parcourant la chaîne de prototypes jusqu’à Object.prototype, où toString est défini. Ce comportement est fondamental en JavaScript, mais il devient dangereux lorsque des attaquants peuvent manipuler ces prototypes.

Qu’est-ce que la pollution de prototype ?

La pollution de prototype est une vulnérabilité qui permet aux attaquants d’injecter des propriétés dans les prototypes des constructions du langage JavaScript, exploitant le fait que JavaScript autorise la modification de tous les attributs d’objet, y compris des propriétés spéciales comme __proto__, constructor, et prototype.

L’attaque fonctionne en exploitant la gestion de l’affectation de propriétés d’objet en JavaScript. Lorsqu’une application fusionne des données contrôlables par l’utilisateur dans des objets existants sans validation appropriée, un attaquant peut créer une entrée malveillante qui modifie le Object.prototype de base. Étant donné que presque tous les objets en JavaScript héritent de Object.prototype, cette modification unique affecte toute l’application.

Ces vulnérabilités apparaissent généralement lorsque des fonctions JavaScript fusionnent récursivement des objets contenant des propriétés contrôlables par l’utilisateur dans des objets existants sans d’abord nettoyer les clés, notamment lors du traitement de données JSON provenant de sources non fiables.

L’anatomie d’une attaque de pollution de prototype

Examinons comment fonctionne une attaque réelle de pollution de prototype en utilisant une fonction de fusion vulnérable, similaire à celles trouvées dans des bibliothèques populaires. Voici un exemple simplifié :

function merge(target, source) {
  for (let key in source) {
    if (typeof source[key] === 'object') {
      if (!target[key]) target[key] = {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

// Usage normal
const user = {};
merge(user, { name: 'Alice', role: 'user' });
console.log(user); // { name: 'Alice', role: 'user' }

Cette fonction semble inoffensive. Elle fusionne récursivement les propriétés d’un objet source dans un objet cible. Cependant, regardez ce qui se passe lorsqu’un attaquant fournit une entrée malveillante :

// Charge utile malveillante
const maliciousInput = JSON.parse('{"__proto__": {"isAdmin": true}}');

// Pollution du prototype
merge({}, maliciousInput);

// Maintenant, TOUS les objets ont la propriété isAdmin
const normalUser = {};
console.log(normalUser.isAdmin); // true - pollution réussie !

L’attaquant a exploité la propriété spéciale __proto__ pour atteindre Object.prototype et injecter une propriété isAdmin. Désormais, chaque objet dans l’application, y compris ceux nouvellement créés qui n’ont jamais été proches de notre fonction de fusion, hérite de cette propriété empoisonnée.

Impact réel : étude de cas Lodash

La bibliothèque populaire Lodash a été affectée par des vulnérabilités de pollution de prototype dans des fonctions comme defaultsDeep, merge, et mergeWith, qui permettent à des utilisateurs malveillants de modifier le prototype de Object via __proto__, pouvant affecter des millions d’applications dans le monde.

Considérez ce code vulnérable utilisant la fonction merge de Lodash :

const _ = require('lodash');
const express = require('express');
const app = express();
app.use(express.json());

app.post('/api/update-settings', (req, res) => {
  const userSettings = {};
  // Vulnérable : fusion de données non fiables
  _.merge(userSettings, req.body);
  
  // Plus tard dans l'application
  if (userSettings.isAdmin) {
    // Accès admin
    return res.json({ access: 'admin' });
  }
  res.json({ access: 'user' });
});

Un attaquant pourrait envoyer cette charge utile :

{
  "__proto__": {
    "isAdmin": true
  }
}

Cette requête unique empoisonne le prototype, et soudain, chaque objet dans l’application, y compris ceux utilisés pour les vérifications d’autorisation, hérite de la propriété isAdmin définie sur true. L’impact se propage à toute l’application.

De la pollution à l’exécution de code à distance

Les conséquences de la pollution de prototype vont bien au-delà d’une simple escalade de privilèges. En environnement Node.js, des attaquants expérimentés peuvent enchaîner la pollution de prototype avec d’autres vulnérabilités pour réaliser une exécution de code à distance (RCE). Cela se produit via une technique appelée “gadget chains”, où des propriétés polluées interagissent avec des chemins de code existants de manière inattendue.

Par exemple, si une application utilise la création de processus enfants et se fie à des propriétés héritées pour la construction de commandes, un attaquant pourrait injecter des commandes malveillantes via pollution de prototype :

// Code vulnérable
const { spawn } = require('child_process');

function executeCommand(options) {
  const defaultOptions = {};
  // Options peuvent hériter de propriétés polluées
  const finalOptions = Object.assign(defaultOptions, options);
  
  spawn('node', [finalOptions.script || 'default.js'], {
    shell: finalOptions.shell || false,
    env: finalOptions.env || process.env
  });
}

Si un attaquant pollue le prototype avec {"shell": true, "script": "; malicious-command"}, il pourrait obtenir une exécution de code lorsque cette fonction s’exécute avec des vérifications de propriété insuffisantes.

Cross-Site Scripting via pollution de prototype

Les attaquants peuvent polluer les prototypes avec des propriétés comme innerHTML, src, ou onerror, et si l’application référence ensuite ces propriétés et les insère dans le DOM, cela devient une vulnérabilité XSS. Cette variante est particulièrement dangereuse dans les frameworks JavaScript côté client :

// Rendu de template vulnérable
function renderContent(element, data) {
  element.innerHTML = data.content || 'Contenu par défaut';
}

// L'attaquant pollue le prototype
const malicious = JSON.parse('{"__proto__": {"content": "<img src=x onerror=alert(document.cookie)>"}}');
merge({}, malicious);

// Plus tard dans le code
const emptyData = {};
renderContent(document.getElementById('output'), emptyData);
// XSS déclenché via prototype pollué!

Détection dans le monde réel

Des chercheurs utilisant des techniques avancées de fuzzing ont découvert 65 nouvelles vulnérabilités de pollution de prototype dans des scénarios zero-day qui ne pouvaient pas être détectés par des méthodes traditionnelles, soulignant la persistance de ce problème. La subtilité de la pollution de prototype est un défi. Contrairement à l’injection SQL ou XSS, qui produisent souvent des erreurs immédiates ou des effets visibles, la pollution de prototype peut rester silencieuse dans votre code, attendant les bonnes conditions pour causer une défaillance catastrophique.

Programmation défensive : construire des applications résistantes à la pollution

Protéger votre application contre la pollution de prototype nécessite une approche multicouche combinant bonnes pratiques de codage, validation d’entrée et décisions architecturales.

Utiliser Object.create(null) pour les dictionnaires

Créer des objets sans prototypes, avec Object.create(null), brise la chaîne de prototypes et empêche la pollution. C’est l’une des défenses les plus efficaces :

// Sécurisé : pas de chaîne de prototype
const userSettings = Object.create(null);
userSettings.name = 'Alice';

// Impossible à polluer
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign(userSettings, malicious);
console.log(userSettings.isAdmin); // undefined - protégé !

Les objets créés de cette manière n’ont pas de prototype, ce qui les rend immunisés contre la pollution de prototype. Utilisez ce pattern pour tout objet contenant des données contrôlables par l’utilisateur.

Valider et assainir les propriétés

Validez et assainissez toujours les clés d’objet avant de traiter les entrées utilisateur :

function secureMerge(target, source) {
  const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
  
  for (let key in source) {
    // Bloquer les clés dangereuses
    if (dangerousKeys.includes(key)) {
      continue;
    }
    // Validation supplémentaire
    if (typeof key !== 'string' || key.startsWith('_')) {
      continue;
    }
    if (typeof source[key] === 'object' && source[key] !== null) {
      if (!target[key]) target[key] = Object.create(null);
      secureMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

Utiliser Object.freeze() pour les objets critiques

Geler les prototypes et objets critiques pour empêcher leur modification :

// Empêcher la pollution de prototype au niveau racine
Object.freeze(Object.prototype);
Object.freeze(Object);

// Geler les objets de configuration
const config = Object.freeze({
  apiUrl: 'https://api.example.com',
  timeout: 5000
});

Bien que cette approche soit efficace, faites attention car elle peut casser du code légitime qui dépend de modifications de prototype. Utilisez-la de manière sélective pour des objets sensibles à la sécurité.

Préférer Map aux objets simples

En pratique recommandée, utilisez Map plutôt qu’un objet pour stocker des paires clé-valeur, car les Maps n’héritent pas de Object.prototype :

// Alternative sécurisée aux objets
const userSettings = new Map();
userSettings.set('name', 'Alice');
userSettings.set('role', 'user');

// Non vulnérable à la pollution de prototype
console.log(userSettings.get('isAdmin')); // undefined

Les Maps offrent une API plus propre pour le stockage clé-valeur et éliminent totalement les risques de pollution de prototype.

Valider le schéma

Utilisez des bibliothèques de validation de schéma pour faire respecter des structures d’objets strictes :

const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().required(),
  email: Joi.string().email().required(),
  role: Joi.string().valid('user', 'admin').required()
}).unknown(false); // Rejette les propriétés inconnues

app.post('/api/users', (req, res) => {
  const { error, value } = userSchema.validate(req.body);
  if (error) {
    return res.status(400).json({ error: error.details });
  }
  // Utiliser en toute sécurité les données validées
  processUser(value);
});

La validation de schéma garantit que seules les propriétés attendues atteignent votre logique applicative, bloquant ainsi toute tentative de pollution de prototype à l’entrée.

Mettre à jour régulièrement les dépendances

Maintenez vos dépendances à jour, notamment les bibliothèques critiques pour la sécurité. Les versions modernes de bibliothèques populaires comme Lodash ont corrigé les vulnérabilités de pollution de prototype, mais uniquement si vous utilisez des versions récentes. Utilisez des outils comme npm audit ou Snyk pour identifier les dépendances vulnérables :

npm audit
npm audit fix

Activer le mode strict

Le mode strict de JavaScript offre des protections supplémentaires :

'use strict';

// Le mode strict empêche la création accidentelle de variables globales
// et fait en sorte que certaines erreurs silencieuses lèvent des exceptions
function processData(input) {
  // Environnement d'exécution plus sûr
}

Tester la pollution de prototype

Incluez des tests de pollution de prototype dans votre suite de tests de sécurité :

describe('Tests de pollution de Prototype', () => {
  it('ne doit pas polluer Object.prototype', () => {
    const original = Object.prototype.toString;
    // Tentative de pollution
    const malicious = { "__proto__": { "isAdmin": true } };
    merge({}, malicious);
    // Vérifier qu'aucune pollution n'a eu lieu
    const testObj = {};
    expect(testObj.isAdmin).toBeUndefined();
    expect(Object.prototype.toString).toBe(original);
  });
  
  it('doit rejeter __proto__ dans l’entrée JSON', () => {
    const input = '{"__proto__": {"polluted": true}}';
    const result = safeJSONParse(input);
    expect({}.polluted).toBeUndefined();
  });
});

La vision d’ensemble : sécurité par défaut

La pollution de prototype illustre un principe plus large en sécurité applicative : la défense en profondeur. Aucune technique unique ne supprime complètement le risque. Au contraire, il faut empiler plusieurs stratégies de défense :

  1. Utiliser des objets sans prototype (Object.create(null)) pour les données utilisateur
  2. Valider et assainir toutes les entrées, notamment les clés d’objet
  3. Geler les objets et prototypes critiques
  4. Préférer Map aux objets simples pour les dictionnaires
  5. Mettre en œuvre une validation stricte du schéma
  6. Mettre à jour régulièrement les dépendances
  7. Effectuer des tests de sécurité réguliers
  8. Surveiller les accès inhabituels aux propriétés en production

Conclusion

La pollution de prototype montre comment une fonctionnalité apparemment anodine du langage peut devenir une vulnérabilité de sécurité grave lorsqu’elle traite des entrées non fiables. En exploitant la chaîne de prototypes de JavaScript, les attaquants peuvent injecter des propriétés qui empoisonnent chaque objet de votre application, conduisant à une escalade de privilèges, une déni de service ou même une exécution de code à distance.

La bonne nouvelle, c’est que la pollution de prototype est évitable grâce à des pratiques de codage disciplinées. En comprenant le vecteur d’attaque, en mettant en œuvre une validation robuste des entrées, en utilisant des patterns de création d’objets plus sûrs et en maintenant vos dépendances à jour, vous pouvez construire des applications JavaScript résistantes à cette vulnérabilité subtile mais dévastatrice.

Souvenez-vous : en JavaScript, votre application n’est aussi sécurisée que sa chaîne de prototypes. Un prototype pollué peut empoisonner toute votre application, mais avec les bonnes stratégies de défense, vous pouvez garantir que vos objets restent purs et que votre application reste sécurisée.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#prototype pollution, JavaScript security vulnerability, prototype chain attack, Object.prototype pollution, proto vulnerability, JavaScript exploit, web application security, Node.js security, Lodash vulnerability, prototype pollution prevention, Object.create null, JavaScript security best practices, prototype pollution attack, CVE JavaScript, secure coding JavaScript, JavaScript RCE, prototype pollution XSS, npm security, JavaScript input validation, Object.freeze security, prototype pollution defense, JavaScript malicious payload, dependency vulnerability, prototype-based inheritance, JavaScript security patterns, web security 2025, OWASP JavaScript, prototype pollution detection, secure merge function, JavaScript sanitization, Map vs Object security, prototype pollution testing, JavaScript security audit, client-side security, server-side JavaScript security, prototype chain exploitation, JavaScript remote code execution, privilege escalation JavaScript, gadget chain attack, JSON security, untrusted input handling, JavaScript prototype manipulation, secure object creation, JavaScript framework security, supply chain security, npm audit, Snyk security, prototype pollution mitigation, defensive programming JavaScript, zero-day JavaScript vulnerability, JavaScript security research, prototype pollution fuzzing, strict mode JavaScript, schema validation security, Joi validation, Express.js security, API security JavaScript, JavaScript security testing, secure development lifecycle, JavaScript threat model, prototype pollution impact, DOM-based XSS, JavaScript injection attack, secure JavaScript patterns, modern JavaScript security, TypeScript security, JavaScript security tools, penetration testing JavaScript, security code review, JavaScript vulnerability assessment, prototype pollution remediation, secure coding standards, JavaScript SAST, dynamic analysis JavaScript, JavaScript security checklist, OWASP Top 10 JavaScript, secure SDLC, JavaScript hardening, runtime protection JavaScript, JavaScript WAF bypass, security automation JavaScript, DevSecOps JavaScript, shift-left security, JavaScript security training, secure JavaScript development, prototype pollution case study, real-world JavaScript attacks, JavaScript security trends 2025, enterprise JavaScript security, microservices security, API gateway security, serverless security JavaScript, cloud security JavaScript, container security Node.js, Kubernetes security JavaScript, CI/CD security, GitHub security scanning, npm package security, open source security, software composition analysis, JavaScript security monitoring, application security testing, bug bounty JavaScript, responsible disclosure, CVE database, NVD JavaScript, security advisory, patch management JavaScript, version control security, code signing, JavaScript obfuscation security, minification security, webpack security, bundler security, frontend security, backend security, full-stack security JavaScript, React security, Vue security, Angular security, Next.js security, Express security, Fastify security, Koa security, JavaScript framework vulnerabilities

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles