flawopen.com/Prototype Pollution/Object.assign()
Schützt Object.assign() vor Prototype Pollution?
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.
// 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] }); } } }
// 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
{ ...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.
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.
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.