Prototype Pollution Attacks

Hard

Prototype pollution is a security vulnerability where an attacker manipulates the prototype chain to inject malicious properties. This can lead to denial of service, property injection, or even remote code execution. It commonly occurs when merging or cloning objects without proper key validation, especially with user-controlled input.

Interactive Visualization

Code
1const user = { name: "Alice" }
2const config = { theme: "dark" }
3
4user.isAdmin // undefined
5config.isAdmin // undefined
6
7// Both objects are clean - no isAdmin property
Objects
user
name:"Alice"
__proto__: → Object.prototype
config
theme:"dark"
__proto__: → Object.prototype
Object.prototype
toString:fn()
Normal state - two clean objects with their own properties
Output:
undefined
undefined
1 / 4
Key Insight: Modifying Object.prototype affects ALL objects. Always use Object.hasOwn() to distinguish own properties from inherited ones.

Key Points

  • Attacker injects properties via __proto__, constructor, or prototype
  • Affected operations: merge, clone, extend, deep assign
  • Can affect all objects if Object.prototype is polluted
  • Prevention: validate keys, use Object.create(null), freeze
  • Famous in lodash merge, jQuery extend, etc.

Code Examples

The Attack Vector

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

// Attacker input
const malicious = JSON.parse('{ "__proto__": { "isAdmin": true } }');

const victim = {};
merge(victim, malicious);

// All objects now have isAdmin: true!
console.log({}.isAdmin);  // true 😱

// The attack:
// merge follows __proto__ and sets {}.__proto__.isAdmin
// This modifies Object.prototype!

Attacker uses __proto__ key to inject properties onto Object.prototype, affecting all objects.

Prevention

// Safe merge - skip dangerous keys
function safeMerge(target, source) {
  for (let key in source) {
    // Skip prototype pollution keys
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
      continue;
    }
    
    if (typeof source[key] === 'object') {
      if (!target[key]) target[key] = {};
      safeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

// Or use Object.create(null) for maps
const safeMap = Object.create(null);
safeMap["__proto__"] = "value";  // Just a property, not the prototype

// Or freeze Object.prototype
Object.freeze(Object.prototype);

Prevent by: 1) Skipping dangerous keys, 2) Using Object.create(null), 3) Freezing Object.prototype.

Common Mistakes

  • Not validating keys in merge/clone functions
  • Using user input directly in object property names
  • Not knowing about Object.freeze(Object.prototype) as protection

Interview Tips

  • Know what prototype pollution is
  • Know the dangerous keys: __proto__, constructor, prototype
  • Know prevention techniques
  • Be aware of affected libraries (lodash < 4.17.12)