VNX-NODE-006 – Prototype Pollution via Merge
Overview
This rule detects deep-merge and deep-assign operations — _.merge(), _.defaultsDeep(), _.set(), lodash.merge(), and Object.assign({}, req.body/req.query) — applied to user-controlled input such as req.body or req.query. If the user input contains a key like __proto__ or constructor, the merge operation walks the prototype chain and injects properties into Object.prototype itself, polluting every object in the process. This is CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes).
Severity: High | CWE: CWE-915 – Improperly Controlled Modification of Dynamically-Determined Object Attributes
Why This Matters
Prototype pollution is deceptively powerful. When Object.prototype is modified, every plain object ({}) in the runtime inherits the injected property. A payload like { "__proto__": { "isAdmin": true } } can elevate privileges silently across the entire application — any subsequent if (user.isAdmin) check becomes true for all users, not just administrators.
In more severe cases, polluting properties used by template engines, module loaders, or serialization libraries can escalate to remote code execution. Multiple CVEs have been disclosed against lodash (CVE-2019-10744, CVE-2020-8203), jQuery (CVE-2019-11358), and other widely used libraries due to prototype pollution in their merge implementations. Lodash versions below 4.17.21 are known to be exploitable. Simply upgrading may not be sufficient if your own code performs the merge.
What Gets Flagged
The rule matches lines containing any of: _.merge(, _.defaultsDeep(, _.set(, lodash.merge(, merge(target, req.body, merge(target, req.query, or Object.assign({}, req.body.
// FLAGGED: lodash merge with user-controlled body
const _ = require('lodash');
app.post('/settings', (req, res) => {
_.merge(defaultConfig, req.body);
res.json(defaultConfig);
});
// FLAGGED: Object.assign spreading request body
const merged = Object.assign({}, req.body, internalDefaults);
An attacker sends: { "__proto__": { "admin": true } } and every object in the application now has .admin === true.
Remediation
Validate and strip prototype-polluting keys before merging. Reject any input that contains
__proto__,constructor, orprototypeas a key at any nesting level:// SAFE: strip dangerous keys before merging function sanitizeInput(obj) { const dangerous = new Set(['__proto__', 'constructor', 'prototype']); if (typeof obj !== 'object' || obj === null) return obj; return Object.fromEntries( Object.entries(obj) .filter(([k]) => !dangerous.has(k)) .map(([k, v]) => [k, sanitizeInput(v)]) ); } app.post('/settings', (req, res) => { const safe = sanitizeInput(req.body); _.merge(defaultConfig, safe); res.json(defaultConfig); });Use
Object.create(null)for dictionary objects that will receive arbitrary keys. Objects created withnullprototype have no__proto__property, so they cannot be used as a pollution vector:// SAFE: null-prototype object cannot pollute Object.prototype const userSettings = Object.create(null); Object.assign(userSettings, req.body);Guard lookups with
Object.prototype.hasOwnProperty.call()rather thanobj.hasOwnProperty(), since a polluted object may shadow that method:// SAFE: explicit hasOwnProperty check if (Object.prototype.hasOwnProperty.call(obj, key)) { // process key }Upgrade lodash to 4.17.21 or higher and use its
_.mergeWithwith a customiser that skips dangerous keys:// SAFE: mergeWith customiser blocks prototype keys _.mergeWith(target, source, (objValue, srcValue, key) => { if (['__proto__', 'constructor', 'prototype'].includes(key)) { return objValue; // keep existing, ignore source } });Use a JSON Schema validator (e.g.,
ajv) to enforce the exact shape of request bodies before any merge operation, rejecting inputs with unexpected keys entirely:npm install ajv
References
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
- CVE-2019-10744 – lodash prototype pollution
- CVE-2020-8203 – lodash prototype pollution
- OWASP Node.js Security Cheat Sheet
- HackerOne – Prototype Pollution report writeup
- lodash documentation – merge
- MITRE ATT&CK T1190 – Exploit Public-Facing Application