FlawOpen

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

Schützt Object.assign() vor Prototype Pollution?

CWE-1321: Prototype PollutionReferenzseite
Kurzantwort

Nicht vollständig. Object.assign() kopiert eigene aufzählbare Eigenschaften durch Zuweisung an das Ziel. Hat die Quelle einen eigenen aufzählbaren Schlüssel __proto__, kann die Zuweisung den geerbten Setter aufrufen und den Prototyp des Zielobjekts ändern. Eine flache Zuweisung ändert nicht selbst das gemeinsame Object.prototype; rekursiver Merge-Code kann jedoch weiterreichende Auswirkungen haben.

UNSICHER — assign und rekursiver Merge
// 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] });
    }
  }
}
SICHER — Spread für flache Kopien; geschützter rekursiver Merge
// 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;
}

Was Object.assign() mit dem Schlüssel __proto__ macht

Object.assign() kopiert jede eigene aufzählbare Quelleigenschaft über das normale Zuweisungsverhalten des Ziels. Hat eine aus JSON gelesene Quelle eine eigene Eigenschaft __proto__ und erbt das Ziel den Legacy-Setter, ändert dieser das [[Prototype]] des Ziels. Dabei entsteht keine wörtliche eigene Eigenschaft __proto__.

Object Spread ({ ...source }) erstellt Dateneigenschaften im Ergebnis; derselbe Quellschlüssel wird zu einer eigenen Dateneigenschaft und ruft den geerbten Setter nicht auf. Das ist für flache Kopien hilfreich, schützt aber nicht vor späteren rekursiven Merges.

Wie rekursive Merges gemeinsame Prototypen verschmutzen können

Prototype Pollution entsteht, wenn unsicherer rekursiver Code angreifergesteuerten Schlüsseln folgt und in einen gemeinsamen Prototyp schreibt. Ein häufiges Muster liest target[key], um ein verschachteltes Objekt zu finden. Ist key __proto__, kann dieser Lesezugriff Object.prototype zurückgeben; der nächste Rekursionsschritt kann dann gewöhnliche Objekte global beeinflussen. Auch Pfade wie constructor.prototype sind relevant.

Prüfen Sie, wo Code mit angreifergesteuerten Schlüsseln in ein Zielobjekt liest oder schreibt, ob dabei Setter aufgerufen werden und ob jede Rekursionsebene gefährliche Schlüssel ablehnt.

FAQ

Ist Object Spread ({ ...obj }) genauso sicher?

Ja, für eine flache Kopie: Spread erzeugt eine eigene Dateneigenschaft und ruft den geerbten Setter nicht auf. Object.assign() kann dagegen mit einem aufzählbaren Quellschlüssel __proto__ den Zielprototyp ändern. Keine der flachen Operationen ändert allein das gemeinsame Object.prototype.

Was ist mit structuredClone()?

structuredClone() erstellt eine separate Kopie und übernimmt die Prototypkette der Quelle nicht. Ein eigener Datenschlüssel __proto__ bleibt in der Kopie ein Datenwert; ein späterer unsicherer Merge kann ihn dennoch speziell interpretieren.

Soll ich einfach eine Bibliothek verwenden?

Verwenden Sie eine gepflegte, aktuelle Merge-Bibliothek und prüfen Sie den Umgang mit __proto__, constructor und prototype auf jeder Verschachtelungsebene. Für mehrere verbreitete Merge- und Object-Path-Hilfen gab es Prototype-Pollution-Advisories; nur korrigierte Versionen enthalten den Schutz.

Quellen

Siehe auch JavaScript Prototype Pollution: Ursachen und Schutz Verursacht JSON.parse() Prototype Pollution? Ist JSON.parse() sicherer als eval()? Ist lodash.merge() vor Prototype Pollution sicher?