Skip to content
Sedat Özdemir
Writing

cybersecurity

Don't Underestimate __proto__: You Might Lose the Whole Kingdom

How a 'sneaky' JSON key turned a 3 AM incident response into a deep dive into Prototype Pollution and its path to Remote Code Execution.

Sedat Özdemir
· 3 dk read

It was 03:22 AM. The Grafana dashboard on my screen showed the main API gateway at testCompany hitting 98% CPU usage, with memory consumption climbing like a classic memory leak. At first, we thought it was a standard DDoS attack, but when I started digging into the traffic packets, I noticed something strange. The incoming requests looked normal, but every JSON payload contained a sneaky hidden key: __proto__. That was the 'aha!' moment: it wasn't just a service crashing; the entire hierarchy of JavaScript objects was being poisoned.

The Heart of the Matter: Messing with JavaScript's DNA

In JavaScript, as you know, everything is an object. And every object has a 'prototype' from which it inherits its properties. This flexibility is great for development, but it can be a security professional's worst nightmare. Prototype Pollution happens when an attacker uses properties like __proto__ or constructor.prototype to modify the global Object structure within the application.

Think about it: your app performs a check like user.isAdmin to authorize a user. If an attacker manages to inject the isAdmin: true property into the global Object prototype, every object created in the system that doesn't already have an isAdmin property defined will automatically return true. Suddenly, everyone is an admin, and everyone is a king!

The Source of the Crisis: An Innocent-Looking Merge Function

The root of the problem we faced that night was a classic 'recursive merge' function used in one of our libraries. A dev friend had written this code to merge two objects (I'm leaving a simplified version here):

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

Looks perfectly fine on paper, right? But things go off the rails when an attacker sends a JSON like this:

{
    "auth": {
        "__proto__": {
            "polluted": "Yes, I am!",
            "isAdmin": true
        }
    }
}

When this payload hits the merge function, the expression target["__proto__"] actually accesses the global Object.prototype. And boom! Now every new object in memory has the polluted property. In our case, the attacker was trying to pave the way for RCE (Remote Code Execution) by 'polluting' a template engine feature that triggered the system's logging mechanism.

The Path to RCE: More Than Just Privilege Escalation!

Many people mistake Prototype Pollution for a simple logic flaw. They think, 'So what? The worst they can do is get into the admin panel.' I wish it were that simple. In the Node.js world, when this vulnerability meets functions like child_process.spawn or popular template engines (like EJS, Pug, or Handlebars), it often turns into a full-blown Remote Code Execution (RCE) vulnerability.

For example, if your application in the background...

Related posts