SecureBlockLog inStart a pentest
Vulnerability Repository
HighClient-Side

Prototype Pollution

Prototype pollution lets attackers inject properties into JavaScript's Object prototype, corrupting application logic and enabling denial of service or RCE.

CVSS 8.1CWE CWE-1321OWASP A03:2021 — Injection

Description

Prototype pollution is a JavaScript-specific vulnerability that occurs when an attacker is able to inject properties into Object.prototype, the root prototype shared by all JavaScript objects. Because every object in JavaScript inherits from Object.prototype, a polluted prototype affects every object created during the lifetime of the application—including internal framework objects, configuration dictionaries, and security-sensitive data structures.

CWE-1321 (Improperly Controlled Modification of Object Prototype Attributes) describes this class of vulnerability precisely. It arises in code that merges, clones, or deeply copies user-supplied objects into application objects without validating keys like __proto__, constructor, or prototype. The vulnerability appears frequently in JavaScript utility libraries (lodash, jQuery extend, hoek), custom object merge functions, and query string parsers that hydrate URL parameters into nested objects.

The severity varies significantly by context. In a browser environment, prototype pollution typically enables XSS by corrupting properties read by rendering frameworks. In Node.js server-side environments, it can escalate to remote code execution by polluting properties used in child_process calls, template engines, or module loaders. Many CVEs in popular npm packages (CVE-2019-10744 in lodash, CVE-2018-3721 in hoek) are prototype pollution vulnerabilities.

How It Works

The vulnerability occurs when user-controlled keys are used to set nested properties without sanitization. Consider a vulnerable deep merge function:

// Vulnerable deep merge (simplified)
function merge(target, source) {
  for (const key of Object.keys(source)) {
    if (typeof source[key] === 'object') {
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];  // No key validation!
    }
  }
}

// Attacker-controlled input
const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, maliciousPayload);

// Now every object in the application inherits isAdmin: true
const user = {};
console.log(user.isAdmin); // true — even on a fresh object!

In a web API context, an attacker sends a crafted JSON request:

POST /api/settings HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer <token>

{
  "__proto__": {
    "isAdmin": true,
    "debug": true
  }
}

For Node.js RCE via template engines, pollution of properties like outputFunctionName in ejs or escapeFunction in pug can inject arbitrary code into template rendering:

// Polluting ejs template engine execution
Object.prototype.outputFunctionName = "x;process.mainModule.require('child_process').execSync('id > /tmp/pwned');x";
// Next ejs.render() call executes the payload

Tools like ppmap and Burp Suite extensions automate prototype pollution discovery in browser and API contexts.

Impact

  • Remote code execution — In Node.js environments, polluted prototype properties reach template engines and child process invocations, enabling arbitrary command execution on the server.
  • Authentication bypass — Properties like isAdmin, isAuthenticated, or role injected into the prototype affect authorization checks across the entire application.
  • Cross-site scripting — Browser-side prototype pollution corrupts DOM manipulation logic in frameworks like jQuery, enabling XSS without a direct injection point.
  • Denial of service — Polluting properties used in critical object comparisons or iterations causes application crashes and service unavailability.
  • Data integrity corruption — Injected default properties silently alter the behavior of data processing functions across all request handlers.

Detection

  1. Review all object merge, clone, and extend operations in the codebase for key validation; search for patterns like target[key] = source[key] in recursive functions.
  2. Run npm audit and cross-reference dependencies against known prototype pollution CVEs; use Snyk or OWASP Dependency-Check for broader coverage.
  3. Send JSON payloads containing __proto__, constructor, and prototype keys to all API endpoints that accept nested JSON objects and observe application behavior changes.
  4. Use the ppmap tool to automatically test browser-side prototype pollution and identify gadget chains that lead to XSS.
  5. Test query string parameters for prototype pollution via qs parser: GET /search?__proto__[isAdmin]=true and observe whether the property appears on subsequent objects.
  6. In Node.js applications, check whether polluted properties reach child_process, eval, or template engine calls using manual code review or static analysis with Semgrep rules.

Remediation

Use safe object merge libraries. Replace custom merge functions with lodash.mergeWith using a customizer that blocks __proto__ keys, or use libraries audited against prototype pollution (lodash >= 4.17.21).

Sanitize user-controlled keys. Validate that keys do not equal __proto__, constructor, or prototype before using them to set object properties:

function safeSet(obj, key, value) {
  if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
    throw new Error('Forbidden key');
  }
  obj[key] = value;
}

Use Object.create(null) for data dictionaries. Objects created with null prototype do not inherit from Object.prototype and are immune to prototype pollution:

const safeMap = Object.create(null); // No prototype chain

Enable --frozen-intrinsics in Node.js. This V8 flag freezes built-in objects, preventing modification of Object.prototype.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping