Co je injektáž __proto__
Shrnutí: `JSON.parse` považuje `__proto__` za obyčejný klíč; škoda vzniká v rekurzivním sloučení, které zapisuje do `Object.prototype`, takže každý objekt zdědí vlastnosti útočníka.

Stručně: zablokujte __proto__, constructor a prototype před každým sloučením, nedůvěryhodný vstup slučujte do Object.create(null) a do CI přidejte assert na prototyp; záplatování knihoven samo nestačí.
V jedné větě: nedůvěryhodný klíč jménem __proto__ potká rekurzivní sloučení a sloučení zapíše do prototypu.
Reprodukce
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
Žádný obchodní objekt nebyl zasažen. byl zasažen Object.prototype a každý objekt teď nese isAdmin.
Proč JSON.parse není vinen
const data = JSON.parse('{"__proto__": {"a": 1}}');
Object.getOwnPropertyNames(data); // [ '__proto__' ]
Object.getPrototypeOf(data) === Object.prototype; // true, nedotčený
__proto__ dorazí jako obyčejná vlastní vlastnost. Nebezpečí je zcela v kódu, který později slučuje, hluboce kopíruje nebo nastavuje podle cesty – _.merge, některé vzory Object.assign a nesčetné ručně psané pomocné funkce set(obj, path, value).
Dopad
- Obejití autorizace:
if (user.isAdmin)se stane nepodmíněně pravdivé - Poškození dat: nečekaná pole se serializují do uložených záznamů
- Odmítnutí služby: velké objekty připojené k prototypu trvale rostou paměť
- Eskalace: v kombinaci se šablonovacími enginy nebo spouštěči příkazů to vedlo ke vzdálenému spuštění kódu
Čtyři obrany
Filtrujte klíče.
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
Blokovat jen __proto__ nestačí: constructor.prototype dosáhne stejného místa.
Slučujte do objektu s nulovým prototypem.
const safeTarget = Object.create(null);
Bez řetězce prototypů je __proto__ jen řetězcový klíč.
Používejte Object.hasOwn místo in. in nahlíží do řetězce prototypů.
Udržujte závislosti záplatované. lodash, deepmerge i json-patch už vydaly opravy pro tuto třídu chyb. Sledujte npm audit.
Ověřte, že to zůstane opravené
Prožeňte vaším slučováním payload obsahující __proto__ a constructor a poté tvrďte:
console.log({}.polluted); // undefined
console.log(Object.prototype.polluted); // undefined
Jeden assert v CI, téměř nulové náklady, zachytí regrese z pozdějších refaktorů.
Časté dotazy
Jaké klíče má sloučení filtrovat?
Blokujte __proto__, constructor a prototype společně. Filtrování jen __proto__ nestačí, protože constructor.prototype dosáhne stejného prototypu.
Proč Object.create(null) zastaví zápis?
Objekt s nulovým prototypem nemá řetězec prototypů, takže __proto__ zůstane obyčejný řetězcový klíč. Funguje jako datový kontejner, ale nenahrazuje filtrování klíčů ani aktualizace závislostí.
Jak testovat prototype pollution v CI?
Pošlete payload obsahující __proto__ a constructor vaším slučováním a poté tvrďte, že {}.polluted a Object.prototype.polluted jsou undefined. Jeden assert nestojí téměř nic a zachytí regrese z pozdějších refaktorů.