JSON Toolkit

Qu'est-ce que l'injection __proto__

L’essentiel : JSON.parse traite __proto__ comme une clé ordinaire ; les dégâts surviennent dans une fusion récursive qui écrit dans Object.prototype, donc chaque objet hérite des propriétés de l'attaquant.

Avancé~5 min de lecturePublié le 2026-10-30Mis à jour le 2026-10-30
Comment la pollution de prototype fonctionne et comment la bloquer

L'essentiel : bloquez __proto__, constructor et prototype avant toute fusion, fusionnez les entrées non fiables dans Object.create(null), et ajoutez une assertion de prototype en CI ; patcher les bibliothèques seul ne suffit pas.

En une phrase : une clé non fiable nommée __proto__ rencontre une fusion récursive, et la fusion écrit dans le prototype.

Une reproduction

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

merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
console.log({}.isAdmin);   // true

Aucun objet métier n'a été touché. Object.prototype l'a été, et chaque objet porte maintenant isAdmin.

Pourquoi JSON.parse n'est pas en cause

const data = JSON.parse('{"__proto__": {"a": 1}}');
Object.getOwnPropertyNames(data);                  // [ '__proto__' ]
Object.getPrototypeOf(data) === Object.prototype;  // true, untouched

__proto__ arrive comme une propriété propre ordinaire. Le danger est entièrement dans le code qui fusionne, copie profondément ou définit par chemin ensuite — _.merge, certains motifs Object.assign, et d'innombrables helpers set(obj, path, value) écrits à la main.

Impact

  • Contournement d'autorisation : if (user.isAdmin) devient inconditionnellement vrai
  • Corruption de données : des champs inattendus se sérialisent dans les enregistrements stockés
  • Déni de service : de grands objets attachés au prototype font croître la mémoire définitivement
  • Escalade : combiné à des moteurs de gabarit ou des exécuteurs de commandes, cela a mené à une exécution de code à distance

Quatre défenses

Filtrez les clés.

const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

Bloquer seulement __proto__ est insuffisant : constructor.prototype atteint le même endroit.

Fusionnez dans un objet à prototype nul.

const safeTarget = Object.create(null);

Sans chaîne de prototypes, __proto__ n'est qu'une clé de chaîne.

Utilisez Object.hasOwn au lieu de in. in consulte la chaîne de prototypes.

Gardez les dépendances patchées. lodash, deepmerge et json-patch ont tous livré des correctifs pour cette classe de bug. Surveillez npm audit.

Vérifier que ça reste corrigé

Passez une charge utile contenant __proto__ et constructor dans votre fusion, puis assertissez :

console.log({}.polluted);                 // undefined
console.log(Object.prototype.polluted);   // undefined

Une assertion en CI, coût quasi nul, attrape les régressions des refactorings ultérieurs.

FAQ

Quelles clés une fusion doit-elle filtrer ?

Bloquez __proto__, constructor et prototype ensemble. Filtrer seulement __proto__ est insuffisant, car constructor.prototype atteint le même prototype.

Pourquoi Object.create(null) arrête-t-il l'écriture ?

Un objet à prototype nul n'a pas de chaîne de prototypes, donc __proto__ reste une clé de chaîne ordinaire. Il fonctionne comme conteneur de données, mais il ne remplace pas le filtrage des clés ni les mises à jour de dépendances.

Comment tester la pollution de prototype en CI ?

Passez une charge utile contenant __proto__ et constructor dans votre fusion, puis assertissez que {}.polluted et Object.prototype.polluted sont undefined. Une assertion ne coûte presque rien et attrape les régressions des refactorings ultérieurs.

Lectures complémentaires