FlawOpen

flawopen.com/Vulnerabilidades/Contaminación de prototipos en JavaScript

Contaminación de prototipos en JavaScript

Gravedad alta CWE-1321 Cadena de suministro y runtime
En sencillo — Un plano compartido

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.

Objetivo:JavaScript del servidor o del navegador que combina claves no confiables o escribe rutas de propiedades controladas por el usuario
Vector:Claves no confiables, como __proto__, o una ruta constructor.prototype que llega a una escritura insegura
Impacto:Depende de un gadget alcanzable: cambios lógicos, denegación de servicio, XSS o, en algunos casos del servidor, ejecución de código; nada es automático
Corrección:Valida con una lista permitida o un esquema; usa Map o diccionarios sin prototipo para claves no confiables

Conceptos 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

Paso 1

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.

Paso 2

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.

Paso 3

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.

Paso 4

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.

utils.js (Fusión recursiva vulnerable)Vulnerable
// 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);
utils.js (Parser de perfil con lista permitida)Protegido
// 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

Ingeniería defensiva y reglas de prevención

Preguntas frecuentes

¿JSON.parse() contamina el prototipo por sí solo?

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?

¿Object.freeze(Object.prototype) lo corrige?

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.

¿TypeScript está protegido?

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.

Explora temas de seguridad y análisis relacionados:Directorio completo de seguridad →