Saltearse al contenido

Prototype Pollution

En JavaScript casi todos los objetos heredan de Object.prototype. Si una aplicación mezcla datos controlados por el atacante en un objeto sin filtrar claves como __proto__, constructor o prototype, el atacante puede escribir en el prototipo global y, con ello, inyectar propiedades que aparecen en todos los objetos del proceso. Eso convierte un merge inocente en control de flujo: desde XSS hasta RCE en Node.

El bug es un escritura de propiedad no saneada que escala a propiedad global. El atacante no modifica un objeto: contamina la clase base de la que todos heredan. Lo peligroso es el “gadget”: algún código legítimo que lee una propiedad que normalmente no existe (options.isAdmin, config.shell, una opción de plantilla) y ahora la encuentra porque está en el prototipo.

Funciones que fusionan/clonan/parsean objetos recursivamente sin bloquear claves peligrosas: merge, extend, clone, defaultsDeep (lodash antiguo), parseo de query strings anidados, JSON.parse + merge, deserializadores.

# vía query/hash que un script fusiona en un objeto de config
https://target/#__proto__[html]=<img src=x onerror=alert(1)>
https://target/?__proto__[innerHTML]=...

Si una librería (jQuery antiguo, sanitizadores, templating) lee una opción contaminada, se obtiene XSS. Detección: en la consola, tras cargar, comprueba Object.prototype.polluted.

# prueba manual en consola
({}).__proto__.test = "pwned";
({}).test; // -> "pwned" => contaminable
# JSON malicioso a un endpoint que hace merge recursivo
POST /api/profile
{"__proto__":{"isAdmin":true}}
  • Auth/logic bypass: contaminar isAdmin, role, flags que luego se leen por defecto.
  • DoS: contaminar propiedades que rompen el runtime.
  • RCE: gadgets conocidos — contaminar opciones que acaban en child_process.spawn (shell, NODE_OPTIONS, env), o en motores de plantillas (EJS, Pug, Handlebars) cuyas opciones compiladas se leen del prototipo → ejecución de código. El RCE depende de encontrar el gadget adecuado en las dependencias.
__proto__ constructor.prototype obj[__proto__][x]
constructor ?__proto__[x]=y {"constructor":{"prototype":{"x":"y"}}}

__proto__ suele filtrarse pero constructor.prototype a veces no.

  • PPScan / ppmap — detección client-side automatizada.
  • Burp Suite — extensión de prototype pollution (server-side) para probar gadgets.
  • DOM Invader (Burp) — detecta sources/sinks de PP en el navegador.

XSS en cliente; en servidor, bypass de autorización, DoS y, con el gadget correcto, RCE completa.

  • Payloads con __proto__, constructor, prototype en JSON, query o path.
  • Comportamiento anómalo global (propiedades que aparecen donde no deberían).

Loguea cuerpos JSON con claves peligrosas; monitoriza versiones de dependencias con CVE de PP conocido.

  • Rechaza/limpia las claves __proto__, constructor, prototype antes de cualquier merge.
  • Usa estructuras sin prototipo: Object.create(null) para mapas de datos; Map en vez de objeto plano.
  • Object.freeze(Object.prototype) donde sea viable; --disable-proto=throw en Node.
  • Valida el input contra un schema estricto (JSON Schema, Zod) que no permita claves extra.
  • Actualiza librerías de merge (lodash ≥ 4.17.12, etc.); usa structuredClone en vez de merges caseros.
  • Evita leer opciones “por si acaso” del objeto sin comprobar que son propias (hasOwnProperty).

Parchea la función de merge/dependencia, audita qué gadgets existían, rota lo que el bypass pudiera haber tocado.

  • CVE-2019-10744 (lodash defaultsDeep) — prototype pollution masivo; una de las referencias.
  • CVE-2018-3721 / CVE-2020-8203 (lodash) — más casos en merge/set.
  • CVE-2019-11358 (jQuery) — prototype pollution en jQuery.extend(true, …) (anterior a 3.4.0); afectó a innumerables sitios.
  • Kibana / various Express apps — cadenas de PP→RCE documentadas por investigadores (Michał Bentkowski, PortSwigger).
  • ¿Hay merge/clone recursivo de input (query, JSON, form anidado)?
  • ¿({}).__proto__.x se contamina tras la petición? (prueba en consola/server)
  • ¿constructor.prototype funciona donde __proto__ está filtrado?
  • Client-side: ¿la contaminación llega a un sink de XSS (innerHTML, src)?
  • Server-side: ¿se puede contaminar isAdmin/role y afectar la lógica?
  • ¿Hay gadgets hacia plantillas o child_process (RCE)?
  • ¿Las claves peligrosas se filtran antes del merge?