Respuesta rápida
Prototype Pollution permite al atacante modificar Object.prototype (o Array.prototype, etc) en JavaScript, contaminando TODOS los objetos del runtime. Los gadgets en libraries (lodash merge, jQuery extend) lo facilitan. Consecuencias: XSS client-side via gadgets de UI libraries, RCE server-side (Node.js execSync con argv override), bypass de auth checks que confían en propiedades del objeto.
Cómo funciona
En JavaScript, todos los objetos heredan de Object.prototype. Si haces:
Object.prototype.isAdmin = true;
const user = {};
console.log(user.isAdmin); // true — heredado de prototype
Si user input llega a obj[key] = value con key === "__proto__":
const obj = {};
obj["__proto__"]["isAdmin"] = true; // contamina Object.prototype
Cualquier {} posteriormente creado tendrá isAdmin = true por defecto.
Vector típico — JSON merge
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
// Atacante envía
const malicious = JSON.parse('{"__proto__":{"isAdmin":true}}');
merge({}, malicious);
// Ahora cualquier objeto tiene isAdmin=true
console.log({}.isAdmin); // true
Lodash _.merge y _.set tuvieron CVEs históricos por este patrón. Patched en versions recientes pero apps con lodash viejo o con merge custom siguen vulnerables.
Server-side (Node.js)
Vector 1: Express body-parser + recursive merge
const _ = require('lodash');
const cfg = require('./config');
app.post('/settings', (req, res) => {
_.merge(cfg, req.body); // ⚠️ user input merged a config global
res.send('ok');
});
// Atacante manda JSON
{ "__proto__": { "polluted": "value" } }
Después de la pollution, cualquier librería en el runtime que tenga code path como:
function callShell(cmd, args) {
const opts = { shell: '/bin/bash' };
if (config.shell) opts.shell = config.shell;
return execSync(cmd, opts);
}
Si config.shell está sin definir explícitamente y el atacante contamina Object.prototype.shell = "node -e 'require(\"child_process\").execSync(...)' ", RCE.
Vector 2: argv override en child_process
Caso real (CVE-2019-7609 Kibana):
// Atacante contamina Object.prototype.NODE_OPTIONS
{ "__proto__": { "NODE_OPTIONS": "--inspect-brk=0.0.0.0:9229" } }
Cuando algún módulo invoca child_process.spawn('node', ['some.js']), Node.js lee NODE_OPTIONS del environment. Si la lib pasa env como objeto y el merge contaminó __proto__.NODE_OPTIONS, el child node abre debugger remoto → conexión + RCE via debugger.
Vector 3: Rendering engine (Pug/Handlebars)
Algunos templates usan Object.assign(defaults, userOptions). Si defaults no se crea con Object.create(null), contamina globals:
const opts = Object.assign({}, userOpts); // userOpts puede tener __proto__
opts[malicious_key] // accede al prototype contaminado
Client-side prototype pollution (CSPP)
Vector: query string o hash que el frontend parsea con un parser vulnerable:
// jQuery extend (vulnerable < 3.4.0)
$.extend(true, options, parsed_hash);
// URL: https://target.tld/#/?__proto__[isAdmin]=true
// Tras parsear, Object.prototype.isAdmin = true
Gadgets en client-side:
- AdSense / Analytics que comprueba propiedades del documento.
- UI library que tiene
if (config.template) document.write(config.template). - Form builder que renderiza inputs según
field.html.
Una vez contaminada Object.prototype.html, cualquier field.html (donde field = {}) vale el atacante. Si la lib hace innerHTML = field.html, XSS persistente en client.
Detección
Burp DOM Invader (CSPP scanner)
Built-in en Burp Pro. Inyecta payloads en location.hash, location.search, body. Detecta gadgets que usan los valores contaminados.
Manual
// Inyectar y ver si Object.prototype cambia
location.hash = '#/?__proto__[test]=polluted';
// Después de la lib parsearlo:
console.log(({}).test); // si "polluted", contamina globals
Server-side detection
POST a endpoints JSON con {"__proto__": {"canary_polluted": "yes"}}. Hacer un GET subsecuente y ver si la app responde con el canary en alguna parte (objeto serializado, error message, etc).
Lista de gadgets útiles
En libraries
- lodash
_.merge,_.set,_.defaultsDeep(pre-4.17.12 vuln). - jQuery
$.extend(true, ...)(pre-3.4.0). - express
req.bodycon qs extended deep (pre-1.13). - mongoose schema merge.
- mootools clase init.
Patrones de código vulnerable
// 1. Recursive merge sin filter de __proto__
function deepMerge(t, s) {
for (const k in s) {
if (typeof s[k] === 'object') deepMerge(t[k] || (t[k] = {}), s[k]);
else t[k] = s[k];
}
}
// 2. Path-based set
function set(obj, path, value) {
const keys = path.split('.');
let curr = obj;
for (let i = 0; i < keys.length - 1; i++) {
if (!curr[keys[i]]) curr[keys[i]] = {};
curr = curr[keys[i]];
}
curr[keys[keys.length - 1]] = value;
}
// 3. Object.assign to globals (raro pero existe)
Object.assign(Object.prototype, userInput); // ⚠️ doble vulnerabilidad
Mitigación correcta
Object.create(null)para objetos que reciben user input. No tienen prototype,__proto__no contamina nada.- Filtrar
__proto__,constructor.prototype,prototypeen merge functions. Object.freeze(Object.prototype)en boot del proceso. Cualquier intento de pollution falla.- JSON.parse reviver que rechaza keys peligrosas.
- TypeScript con tipos estrictos evita muchos casos por design.
- Lodash 4.17.12+, jQuery 3.4.0+, etc.
Hunting checklist
- ¿La app usa lodash, jQuery, mootools, o frameworks viejos?
- ¿Hay endpoints que aceptan JSON deeply merged a state?
- ¿El frontend parsea
location.hasholocation.searchcon jQuery extend? - ¿Probar
{"__proto__":{"canary":"polluted"}}y verificar si propaga? - ¿Burp DOM Invader detecta gadgets?
- ¿Tras pollution, hay paths de código que usan propiedades sin verificar (config.shell, options.html)?
Labs relacionados
Practica prototype pollution client + server side y gadget chains hacia XSS/RCE: labs de Prototype Pollution.
Practica esto en un lab
Prototype Pollution
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
Análisis JavaScript client-side — endpoints, secrets y source maps
Extracción de endpoints ocultos de JS bundles, detección de secrets, análisis de source maps y dynamic instrumentation con Frida para auditar lógica client-side.
Client-side admin bypass — boolean manipulation + BAC en SPA moderna
Report real Quora: SPA con isAdmin boolean en localStorage que controla UI + backend que no valida server-side. Cómo encadenar boolean flip con BAC para admin takeover.
DOM XSS — gadgets, postMessage handlers y CVE-2025-59840
DOM XSS no es solo innerHTML. Sources/sinks, gadget chains via toString(), postMessage handlers sin origin check, hash-based routing rotos.