FlawOpen

flawopen.com/Contaminación de prototipos/JSON.parse()

¿JSON.parse() causa contaminación de prototipos?

CWE-1321: Contaminación de prototiposGuía de referencia
Respuesta breve

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.

COPIA INSEGURA — DESTINO LOCAL
// 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
MANEJO SEGURO — FORMA CONOCIDA
// 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

¿Debo usar siempre un reviver para quitar __proto__?

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.

¿Express o body-parser me protegen?

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.

¿JSON.stringify() supone un riesgo de contaminación de prototipos?

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

Véase también Contaminación de prototipos JS: causas y solución ¿JSON.parse() es más seguro que eval()? ¿lodash.merge() causa contaminación de prototipos? Object.assign() y Prototype Pollution: ¿qué es seguro?