FlawOpen

flawopen.com/Prototype Pollution/Object.assign()

Object.assign() protège-t-il de Prototype Pollution ?

CWE-1321: Prototype PollutionPage de référence
Réponse courte

Pas complètement. Object.assign() copie les propriétés propres énumérables en les affectant à la cible. Si la source possède une clé propre énumérable __proto__, cette affectation peut appeler le setter hérité et modifier le prototype de l’objet cible. Une affectation superficielle ne modifie pas à elle seule Object.prototype partagé ; une fusion récursive peut avoir un impact plus large.

NON SÛR — assign et fusion récursive
// A JSON own "__proto__" key reaches the inherited setter
const source = JSON.parse('{"__proto__":{"isAdmin":true}}');
const target = Object.assign({}, source);
// target's prototype changes; Object.prototype does not.

// Recursive merge can reach Object.prototype
function deepAssign(target, source) {
  for (const key of Object.keys(source)) {
    if (source[key] && typeof source[key] === 'object') {
      target[key] = target[key] || {};
      deepAssign(target[key], source[key]);
      // With key === '__proto__', target[key] can be
      // Object.prototype; recursion then writes into it.
    } else {
      Object.assign(target, { [key]: source[key] });
    }
  }
}
SÛR — spread superficiel et fusion récursive protégée
// Spread creates an own data property; it does not invoke the setter.
const shallow = { ...userInput };

// Reject dangerous keys at every depth and use own data properties.
const blocked = new Set(['__proto__', 'constructor', 'prototype']);
function isRecord(value) {
  if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
  const proto = Object.getPrototypeOf(value);
  return proto === Object.prototype || proto === null;
}
function copySafe(value) {
  if (Array.isArray(value)) return value.map(copySafe);
  return isRecord(value) ? mergeSafe(value) : value;
}
function mergeSafe(source, target = Object.create(null)) {
  if (!isRecord(source)) throw new TypeError('Expected a plain object');
  for (const key of Object.keys(source)) {
    if (blocked.has(key)) continue;
    const value = source[key];
    const current = Object.hasOwn(target, key) ? target[key] : undefined;
    const next = isRecord(value)
      ? mergeSafe(value, isRecord(current) ? current : Object.create(null))
      : copySafe(value);
    Object.defineProperty(target, key, {
      value: next, enumerable: true, writable: true, configurable: true
    });
  }
  return target;
}

Ce que fait Object.assign() avec la clé __proto__

Object.assign() copie chaque propriété propre énumérable de la source par le mécanisme normal d’affectation de la cible. Si une source issue de JSON possède une propriété propre __proto__ et que la cible hérite du setter historique, celui-ci modifie le [[Prototype]] de la cible. Aucune propriété propre littérale __proto__ n’est créée dans ce cas.

Le spread d’objet ({ ...source }) crée des propriétés de données dans le résultat ; la même clé devient une propriété propre et n’appelle pas le setter hérité. Cette différence est utile pour une copie superficielle, mais ne protège pas les fusions récursives ultérieures.

Comment les fusions récursives peuvent polluer des prototypes partagés

Prototype Pollution se produit quand du code récursif non sûr suit des clés contrôlées par l’attaquant et écrit dans un prototype partagé. Un motif courant lit target[key] pour trouver un objet imbriqué. Si key vaut __proto__, cette lecture peut renvoyer Object.prototype ; la récursion suivante peut alors affecter globalement les objets ordinaires. Les chemins comme constructor.prototype sont aussi à examiner.

En revue, cherchez les lectures et écritures dans la cible à l’aide de clés contrôlées par l’attaquant, vérifiez les appels de setters et assurez-vous que chaque niveau récursif rejette les clés dangereuses.

FAQ

Le spread d’objet ({ ...obj }) est-il aussi sûr ?

Oui pour une copie superficielle : spread crée une propriété de données propre et n’appelle pas le setter hérité. À l’inverse, Object.assign() peut modifier le prototype cible avec une clé source énumérable __proto__. Aucune des deux opérations superficielles ne modifie seule Object.prototype partagé.

Qu’en est-il de structuredClone() ?

structuredClone() crée une copie séparée sans appliquer la chaîne de prototypes de la source. Une clé de données propre __proto__ reste une donnée dans la copie ; une fusion non sûre ultérieure peut encore l’interpréter spécialement.

Puis-je simplement utiliser une bibliothèque ?

Utilisez une bibliothèque de fusion maintenue et à jour ; vérifiez son traitement de __proto__, constructor et prototype à chaque niveau. Plusieurs utilitaires populaires ont fait l’objet d’avis de Prototype Pollution ; seules les versions corrigées incluent le correctif.

Références

À voir aussi Pollution de prototypes JavaScript : causes et correctifs JSON.parse() provoque-t-il une pollution de prototype ? JSON.parse() est-il plus sûr que eval() ? lodash.merge() : risque de pollution de prototypes ?