Skip to main content
Offensive360
Home / Knowledge Base / Prototype Pollution
High CWE-1321 A03:2021 Injection

Prototype Pollution

Prototype pollution is a JavaScript vulnerability that allows attackers to inject properties into Object.prototype, affecting all objects in the application and potentially leading to RCE or privilege escalation.

Affects: JavaScript

What is Prototype Pollution?

Prototype pollution is a JavaScript vulnerability where an attacker can inject properties into Object.prototype — the base prototype from which all JavaScript objects inherit. Because properties on Object.prototype are accessible on every plain object, polluting it can change the behavior of the entire application, override security checks, and in some environments lead to Remote Code Execution.

The vulnerability commonly appears in deep merge, deep clone, or recursive property assignment utilities when they process attacker-controlled JSON without checking for dangerous keys like __proto__, constructor, or prototype.

How exploitation works

A deep merge function copies properties from a source object to a target without checking for __proto__:

function merge(target, source) {
    for (const key in source) {
        if (typeof source[key] === 'object') {
            merge(target[key] || (target[key] = {}), source[key]);
        } else {
            target[key] = source[key];
        }
    }
}

// Attacker sends: { "__proto__": { "isAdmin": true } }
merge({}, JSON.parse(attackerInput));

// Now EVERY object inherits isAdmin: true
console.log({}.isAdmin); // true — security check bypassed

In Node.js with template engines like Handlebars or Pug, prototype pollution can escalate to RCE by injecting properties that influence template compilation.

Vulnerable code examples

Custom deep merge

// VULNERABLE: No key sanitization — processes __proto__ as a regular key
function deepMerge(dst, src) {
    Object.keys(src).forEach(key => {
        if (typeof src[key] === 'object' && src[key] !== null) {
            deepMerge(dst[key] = dst[key] || {}, src[key]);
        } else {
            dst[key] = src[key];
        }
    });
    return dst;
}

// Vulnerable call
app.post('/settings', (req, res) => {
    const config = deepMerge(defaultConfig, req.body); // req.body is attacker-controlled
});

Vulnerable lodash (pre-4.17.5)

// VULNERABLE: Older lodash _.merge() was susceptible to prototype pollution
const _ = require('lodash'); // version < 4.17.5
_.merge({}, JSON.parse(userInput));

Secure code examples

Safe merge with key allowlist

// SECURE: Block dangerous prototype keys explicitly
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

function safeMerge(dst, src) {
    Object.keys(src).forEach(key => {
        if (FORBIDDEN_KEYS.has(key)) return; // Skip dangerous keys
        if (typeof src[key] === 'object' && src[key] !== null) {
            safeMerge(dst[key] = dst[key] || Object.create(null), src[key]);
        } else {
            dst[key] = src[key];
        }
    });
    return dst;
}

Use null-prototype objects for untrusted data

// SECURE: Objects without prototype cannot pollute Object.prototype
function processUserSettings(userInput) {
    const parsed = JSON.parse(userInput);
    // Create storage with no prototype chain
    const safe = Object.assign(Object.create(null), parsed);
    // Process safe without risk of inherited pollution
    return safe;
}

What Offensive360 detects

  • Unsafe deep merge/clone — Recursive object property assignment without __proto__, constructor, or prototype key checks
  • Bracket-notation assignment with user keysobj[userKey] = value patterns where userKey is tainted
  • Unpatched utility library versions — Known-vulnerable versions of lodash, jQuery, or minimist used in package.json
  • Object.assign with nested user objects — Shallow assign is safe, but combined with other operations can still be exploited

Remediation guidance

  1. Block dangerous prototype keys — In any recursive merge or property assignment, explicitly reject __proto__, constructor, and prototype as keys.

  2. Use Object.create(null) for data storage — Objects created with a null prototype have no prototype chain and cannot pollute Object.prototype.

  3. Use hasOwnProperty checks — When iterating object keys, use Object.prototype.hasOwnProperty.call(obj, key) rather than in operator or inherited enumeration.

  4. Keep dependencies updated — Prototype pollution was patched in lodash 4.17.5+, jQuery 3.4.0+, and minimist 0.2.1+. Audit and update regularly.

  5. Freeze Object.prototypeObject.freeze(Object.prototype) prevents property injection at runtime. Test thoroughly for compatibility with third-party libraries.

References

By Offensive360 Security Research Reviewed: March 2026

Detect Prototype Pollution automatically

Run Offensive360 SAST on your codebase to find this and 100+ other vulnerabilities.