Prototype Pollution

Overview

Prototype pollution happens when attacker-controlled input is used to write properties into JavaScript object prototypes instead of only into a safe application object. Because many objects inherit from the same prototype chain, one polluted property can affect large parts of the application at once. On its own this may look subtle, but once combined with sinks such as XSS, privilege checks, template rendering, or method overrides, it can become serious. These notes cover the core model, dangerous property paths, common vulnerable patterns, exploit impact, and useful GitHub tooling references.

JavaScript Prototype Basics

Every JavaScript object is linked to a prototype object. If a property is not found directly on the object, JavaScript looks up the prototype chain.

That inheritance model is what makes prototype pollution dangerous: one write to a shared prototype can change behavior for many objects that were never meant to be modified.

Why Prototype Pollution Matters

Prototype pollution is usually not the final impact by itself. The real damage comes when the polluted property is later used by sensitive logic.

Dangerous Keys

The classic keys to watch are:

These matter because attacker-controlled expressions like the following can write into the prototype chain instead of just into ordinary data:

obj[x][y] = value
obj[x][y][z] = value

If x becomes __proto__, or if x and y become constructor and prototype, a shared prototype may be modified.

Path-Based Property Setters

One of the most common sources is a helper that sets a value using a user-controlled path, especially utility functions like Lodash _.set().

_.set(friend, input.path, input.value)

If the path is safe, this is just convenient object manipulation. If the path is attacker-controlled, it can become prototype pollution.

Harmless example:

{"path":"reviews[0].content","value":"<script>alert('x')</script>"}

Dangerous example:

{"path":"__proto__.isAdmin","value":true}

Once polluted, later logic that checks inherited properties may behave incorrectly.

Property Injection and XSS

A realistic impact is chaining prototype pollution into stored or reflected XSS. If a polluted property is later rendered into the DOM or a server-side template, a property that did not originally exist may suddenly appear across many objects.

That is why prototype pollution is often a force multiplier rather than a standalone end state.

Recursive Merge Abuse

Another classic vulnerable pattern is recursive object merge logic that copies keys deeply without filtering prototype-related properties.

function recursiveMerge(target, source) {
    for (let key in source) {
        if (source[key] instanceof Object) {
            if (!target[key]) target[key] = {};
            recursiveMerge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
}

If the source object contains __proto__ or a constructor.prototype path, the merge can accidentally write into shared prototype state.

Clone and Copy Operations

Deep cloning or object-copy helpers can also become dangerous when they traverse inherited properties or unsafe keys. A clone operation that “helpfully” copies everything may carry polluted state into places the developer never expected.

Anywhere the application merges, clones, defaults, or extends objects using user-influenced data deserves review.

DoS Through Method Overwrite

Prototype pollution can also crash or destabilize an application by overriding common built-in methods or properties. For example, polluting something like Object.prototype.toString can break code far outside the original input path.

If a heavily used method is replaced with a non-function or with unexpected behavior, the result may be a broad application failure rather than just a local bug.

What To Look For

Testing Workflow

GitHub Links

These repositories are worth keeping handy during review and validation:

Mitigation

Key Takeaways