Prototype Pollution Attacks

Prototype Pollution Attacks

Prototype pollution is a JavaScript-specific vulnerability where attackers modify the prototype of base objects like Object.prototype or Array.prototype, affecting all objects in the application. This can lead to denial of service, property injection, and even remote code execution in certain scenarios. The vulnerability typically arises from unsafe merge or clone operations that don't properly validate object keys.

// Understanding prototype pollution

// VULNERABLE: Unsafe object merge
function unsafeMerge(target, source) {
    for (let key in source) {
        if (typeof source[key] === 'object' && source[key] !== null) {
            if (!(key in target)) {
                target[key] = {};
            }
            unsafeMerge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

// Attack example
const maliciousPayload = {
    "__proto__": {
        "isAdmin": true,
        "polluted": "yes"
    }
};

const userObject = {};
unsafeMerge(userObject, maliciousPayload);

// Now all objects have isAdmin: true!
console.log({}.isAdmin);  // true - prototype pollution successful

// SECURE: Safe merge with prototype checking
function safeMerge(target, source) {
    for (let key in source) {
        // Block prototype pollution keys
        if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
            continue;
        }
        
        // Additional check for prototype chain
        if (!source.hasOwnProperty(key)) {
            continue;
        }
        
        if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
            if (!(key in target)) {
                target[key] = {};
            }
            safeMerge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

// SECURE: Using Map for dynamic properties
class SecureUserPreferences {
    constructor() {
        // Use Map instead of plain objects for user-controlled keys
        this.preferences = new Map();
    }
    
    setPreference(key, value) {
        // Maps don't have prototype pollution issues
        if (typeof key !== 'string') {
            throw new Error('Key must be a string');
        }
        
        // Additional validation
        if (key.length > 100 || !/^[a-zA-Z0-9_-]+$/.test(key)) {
            throw new Error('Invalid preference key');
        }
        
        this.preferences.set(key, value);
    }
    
    getPreference(key) {
        return this.preferences.get(key);
    }
}

// SECURE: Object.create(null) for safe objects
function createSafeObject() {
    // Objects without prototype chain
    const safeObj = Object.create(null);
    
    // Safe property assignment
    return new Proxy(safeObj, {
        set(target, property, value) {
            if (property === '__proto__' || property === 'constructor') {
                throw new Error('Illegal property name');
            }
            target[property] = value;
            return true;
        }
    });
}