flawopen.com/Vulnerabilidades/Contaminación de prototipos en JavaScript
Contaminación de prototipos en JavaScript
Imagina un taller donde cada aparato nuevo consulta un reglamento compartido cuando sus propios ajustes no responden a una pregunta. Si alguien escribe allí una regla controlada por un atacante, otros aparatos podrían tomarla como predeterminada. En la contaminación de prototipos, las escrituras dinámicas inseguras pueden alterar el prototipo de un objeto, a veces Object.prototype. El efecto no es necesariamente global: el impacto requiere código que después confíe en la propiedad heredada.
__proto__, o una ruta constructor.prototype que llega a una escritura inseguraMap o diccionarios sin prototipo para claves no confiablesConceptos clave y términos del subsistema
- Object.prototype
- Prototipo compartido que heredan los objetos ordinarios. Su contaminación puede afectar a muchos objetos, pero no a todos los objetos JavaScript ni a todos los realms.
- __proto__
- Accesor heredado de objetos ordinarios que obtiene o establece su prototipo. En texto JSON, la misma grafía es un nombre de propiedad normal hasta que otro código la maneja de forma insegura.
- Fusión recursiva profunda
- Función que recorre claves anidadas y las escribe en otro objeto. Es peligrosa cuando claves no confiables pueden seleccionar propiedades relacionadas con prototipos o rutas inseguras.
- CWE-1321
- Modificación indebidamente controlada de atributos del prototipo de objetos. El fallo es la mutación sin control; el impacto posterior depende del comportamiento alcanzable de la aplicación.
Flujo del ataque, paso a paso
Entrada de propiedades no confiables
Un endpoint acepta claves de objeto controladas por el atacante. Analizar JSON con una propiedad propia __proto__ no modifica por sí solo un prototipo.
Escritura dinámica insegura
Una combinación recursiva o un setter de rutas sigue esas claves y realiza escrituras dinámicas sin validar cada segmento.
Cambio del prototipo propio o compartido
Según el destino y el orden de las escrituras, la operación cambia el prototipo del destino o llega a uno compartido como Object.prototype. Son resultados distintos.
Un gadget determina el impacto
Un gadget posterior lee el valor heredado durante una operación sensible a la seguridad. El resultado depende de ese gadget; no se debe asumir una evasión de autenticación ni ejecución de código.
Mecanismo y causa raíz
La búsqueda de propiedades en JavaScript recorre la cadena de prototipos. Una combinación vulnerable o un setter de rutas puede permitir que claves no confiables, como __proto__ o constructor.prototype, dirijan una escritura a un prototipo. Un campo propio __proto__ creado por JSON.parse no es contaminación por sí solo; otro código debe interpretarlo mediante una asignación insegura. Cambiar el prototipo de un destino afecta a ese destino; modificar Object.prototype afecta a los objetos ordinarios que lo heredan. Hace falta otro gadget que consuma la propiedad heredada para que exista impacto de seguridad.
// VULNERABLE: claves controladas por el atacante llegan a escrituras recursivas
function merge(target, source) {
for (const key in source) {
const value = source[key];
if (value && typeof value === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], value);
} else {
target[key] = value;
}
}
return target;
}
// JSON.parse crea una propiedad propia __proto__; la combinación insegura es el riesgo.
const input = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, input);
// PROTEGIDO: copia solo los campos que acepta este endpoint
const PROFILE_KEYS = new Set(['displayName', 'theme']);
function parseProfile(input) {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new TypeError('profile must be an object');
}
const profile = Object.create(null);
for (const key of Object.keys(input)) {
if (!PROFILE_KEYS.has(key)) continue;
const value = input[key];
if (typeof value !== 'string' || value.length > 80) {
throw new TypeError('invalid profile field');
}
profile[key] = value;
}
return profile;
}
Secuencia del ataque y explotación
- 1Entrada: una solicitud incluye una clave de objeto inesperada, como
{"__proto__": {"admin": true}}. Analizar JSON no modifica por sí solo un prototipo. - 2Escritura insegura: una combinación genérica o un setter de rutas procesa la clave sin una lista permitida.
- 3Acceso al prototipo: la escritura puede cambiar el prototipo de un destino o llegar a uno compartido, según la ruta del código.
- 4Gadget de impacto: la contaminación solo afecta una decisión sensible si el código posterior confía en una propiedad heredada. Prueba el gadget y el flujo de autorización reales.
Ingeniería defensiva y reglas de prevención
- ✓Prefiere listas permitidas por esquema: construye el destino solo con los campos que acepta el endpoint. Si no se puede evitar un escritor genérico de rutas, rechaza segmentos relacionados con prototipos en cada nivel.
- ✓Usa diccionarios seguros: almacena claves arbitrarias en
Mapo un diccionarioObject.create(null); valida antes de entregar datos a API que esperan objetos ordinarios. - ✓Comprueba propiedades propias: usa
Object.hasOwn(record, key)para verificaciones sensibles; no permitas que valores heredados autoricen acciones. - ✓Usa el endurecimiento del runtime como defensa en profundidad:
--disable-proto=throwde Node.js elimina una vía, pero no bloqueaconstructor.prototype. Congelar objetos nativos puede romper polyfills y dependencias; evalúa la compatibilidad y nunca lo uses como sustituto de escrituras seguras.
Preguntas frecuentes
No. JSON.parse() crea una propiedad propia llamada __proto__; no invoca el setter heredado. El riesgo aparece si una fusión o asignación posterior activa ese setter. Consulta ¿JSON.parse() causa Prototype Pollution?
Puede impedir escrituras en el propio Object.prototype congelado, pero no evita que un setter cambie el prototipo de un objeto individual ni protege todos los demás prototipos. También puede romper código que amplía objetos incorporados. Úsalo solo como defensa en profundidad, no como solución del manejo de datos.
No. Los tipos se eliminan en tiempo de ejecución. Un valor declarado como Record<string, string> aún puede contener una clave __proto__; valida claves no confiables y crea objetos de forma segura.
- MDN — Contaminación de prototipos en JavaScript
- OWASP — Guía para prevenir prototype pollution
- CWE-1321 — Modificación indebida de atributos del prototipo de objetos
- MDN — accessor Object.prototype.__proto__