Prototype Pollution Exploitation in JavaScript

Manish Garg
Manish Garg Associate of (ISC)² · RingSafe
Apr 25, 2026
2 min read

Last updated: April 26, 2026

Prototype pollution is a JavaScript-specific bug where attackers modify the Object.prototype, affecting every object in the application. Originally a curiosity, prototype pollution gained mainstream attention with multiple high-profile CVEs in popular npm packages (lodash, jQuery, and many more). This article covers detection, exploitation, and the modern defences.

The bug

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

// Attacker payload:
const evil = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, evil);
// Now every object has isAdmin = true, including objects elsewhere in the app
({}).isAdmin // true

The __proto__ key isn’t a regular property — it’s the chain to Object.prototype. Setting it modifies the global prototype, affecting every object created from then on.

Common sinks

  • lodash.merge, lodash.set, lodash.defaultsDeep (all had CVEs)
  • jQuery.extend(true, ...) (deep copy)
  • Custom merge / setNested / cloneDeep functions
  • Express body-parser configurations that allow raw JSON without validation
  • YAML / TOML / config-loaders that allow merging

Detection

# Static analysis
semgrep --config "p/javascript" --include "*.js" --include "*.ts" .

# Look for vulnerable patterns
grep -rE "lodash\.(merge|set|defaultsDeep)|\\\$\\.extend\\(true|JSON\\.parse\\(.*req\\." src/

# Test endpoints by sending payloads with __proto__ keys
curl -X POST https://target/api/config \
  -H "Content-Type: application/json" \
  -d '{"__proto__": {"polluted": "yes"}}'

# Then test if pollution worked:
curl https://target/api/health
# Response with extra "polluted": "yes" field = vulnerable

Exploitation paths

  • Authentication bypass — pollute isAdmin, role, permissions default values
  • RCE in Node.js — pollute prototype properties used by template engines or child_process spawn (chained vulnerabilities)
  • DoS — pollute toString or prototype methods used in critical paths
  • Logic bypass — pollute defaults that flow into business logic

The fix

  • Use Object.create(null) for objects that hold user input — no prototype
  • Validate keys — reject keys named __proto__, prototype, constructor
  • Use Map / Set instead of plain objects where possible
  • Update vulnerable dependencies — lodash 4.17.21+, jQuery 3.5.0+
  • Object.freeze(Object.prototype) at app startup — prevents pollution at runtime; some libraries break with this
// Safe merge example
function safeMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = target[key] || Object.create(null);
      safeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

Compliance angle

  • OWASP Top 10 A03 (Injection) — prototype pollution falls under broader injection class
  • DPDP §8(5) — exploitation leading to authentication bypass is reasonable-security failure

The takeaway

Prototype pollution is JavaScript’s quiet bug class. Static analysis catches obvious cases; manual review catches subtle ones. The fix is library updates + key validation + Object.create(null) for user-input-holding objects. Audit your Node.js dependencies for known-vulnerable versions of merge utilities.

Need a real pentest?

Get a VAPT scoping call

Senior practitioner-led VAPT — not a checklist run by juniors. CVSS-scored findings, free retest, attestation letter. India's SMBs and SaaS teams.

Book VAPT scoping call Replies in 4 working hrs · India-only · Senior consultants