flawopen.com/Contaminación de prototipos/JSON.parse()
¿JSON.parse() causa contaminación de prototipos?
No. JSON.parse() crea una propiedad propia llamada __proto__; el análisis no invoca el setter heredado ni cambia un prototipo. El riesgo aparece después, si otro código copia esa clave mediante un setter, combina recursivamente claves no confiables o escribe en una ruta de objeto controlada por quien ataca.
// JSON.parse crea datos; no contamina un prototipo. const input = '{"__proto__":{"isAdmin":true}}'; const parsed = JSON.parse(input); // Object.assign usa Set en el destino. Aquí solo cambia // el prototipo de este objeto local de demostración. const copied = Object.assign({}, parsed); Object.getPrototypeOf(copied).isAdmin; // true Object.prototype.isAdmin; // undefined
// Analiza y construye solo los campos que acepta la aplicación. const input = '{"theme":"dark","locale":"en","__proto__":{"isAdmin":true}}'; const parsed = JSON.parse(input); const config = { theme: parsed.theme, locale: parsed.locale, }; // Para mapas con claves no confiables, evita setters heredados. const options = Object.assign(Object.create(null), parsed);
Por qué JSON.parse() no es el punto de contaminación
Una clave JSON y el setter de prototipo de un literal de objeto son operaciones distintas. El análisis JSON define propiedades propias en el nuevo valor; no ejecuta obj.__proto__ = value. Las operaciones posteriores pueden comportarse de otra manera: Object.assign(target, source) asigna en el destino, así que una clave propia __proto__ puede activar el setter heredado del destino. Eso cambia el prototipo del objeto destino; por sí solo, no modifica Object.prototype.
Después de const o = JSON.parse('{"__proto__":{"a":1}}'), Object.hasOwn(o, "__proto__") es verdadero y Object.prototype.a sigue sin definirse. Si una copia posterior asigna esa clave a un destino normal, el destino puede recibir otro prototipo. La expansión de objetos crea propiedades propias sin invocar el setter, pero no protege frente a fusiones recursivas o rutas inseguras.
Dónde aparece el riesgo real
El JSON analizado son datos no confiables, no un prototipo contaminado. El peligro depende de los puntos de escritura posteriores: una fusión recursiva, un setter de rutas o un bucle de copia que asigna claves a un objeto ordinario. Una fusión recursiva vulnerable puede seguir __proto__ o constructor.prototype hasta un prototipo compartido; una copia de un nivel puede cambiar solo su destino. Traza la función exacta y el objeto destino antes de afirmar el impacto.
Revisa dónde se escriben claves no confiables, no solo dónde se analiza el JSON: funciones de fusión profunda, setters de rutas y bucles for...in que asignan propiedades. Rechaza __proto__, constructor y prototype si el usuario puede controlar las claves; prefiere validar una forma conocida o usar un diccionario sin prototipo.
Preguntas frecuentes
Un reviver puede eliminar esa clave durante el análisis, lo que sirve si la aplicación no la necesita. Es defensa en profundidad, no sustituye la corrección de fusiones y rutas inseguras; constructor.prototype y entradas que no sean JSON también pueden ser relevantes.
El parser del cuerpo produce un valor JavaScript; no hace seguras las copias o fusiones posteriores. Revisa el parser concreto y las operaciones siguientes. Algunos parsers de query strings también crean objetos anidados a partir de corchetes, que es otra vía de entrada.
No. La serialización lee valores y produce texto JSON; no cambia el prototipo. El riesgo puede volver cuando ese texto se analiza y pasa a un punto inseguro.
Referencias
- MDN — Contaminación de prototipos en JavaScript
- MDN — JSON.parse()
- OWASP — Prevención de contaminación de prototipos
- CWE-1321 — Modificación no controlada de atributos del prototipo de objeto
- ¿Qué es la contaminación de prototipos?
- ¿Es Object.assign() seguro frente a la contaminación de prototipos?
- ¿Es JSON.parse() más seguro que eval()?