JSON Toolkit

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.

Pokročilý~5 min čteníPublikováno 2026-10-30Aktualizováno 2026-10-30
Jak prototype pollution funguje a jak ho zablokovat

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ů.

Další čtení