Kuboid
Open Luck·Kuboid.in
Black Hat2023
Open in YouTube ↗

Prototype Pollution Leads to RCE: Gadgets Everywhere

Black Hat5,167 views39:36over 2 years ago

This talk demonstrates how prototype pollution vulnerabilities in Node.js applications can be escalated to remote code execution (RCE) by identifying and chaining specific 'gadgets' within the application or its dependencies. The speaker details the mechanics of prototype-based inheritance in JavaScript and how it can be abused to manipulate application behavior, such as overriding environment variables or file paths. The presentation provides practical methodologies for both static and dynamic analysis to detect these gadgets and offers defensive strategies, including the use of null-prototype objects and property filtering. The research highlights several RCE vulnerabilities discovered in popular open-source projects like Parse Server and Rocket.Chat.

From Prototype Pollution to RCE: Exploiting Node.js Gadget Chains

TLDR: Prototype pollution in Node.js is often dismissed as a low-impact bug, but it can be a powerful primitive for Remote Code Execution (RCE) when chained with specific application gadgets. By manipulating object prototypes, researchers can override environment variables or file paths to hijack execution flows in popular software like Parse Server and Kibana. Pentesters should focus on identifying these gadget chains during code review or dynamic analysis to turn seemingly minor pollution bugs into full system compromise.

JavaScript’s prototype-based inheritance is a double-edged sword. While it provides flexibility for developers, it creates a massive attack surface when user-controlled input can modify the prototype of base objects. For years, the security community treated prototype pollution as a curiosity—a way to cause a denial of service or perhaps manipulate some client-side logic. That perspective is outdated. Modern research proves that when you can pollute the prototype, you can often reach deep into the application’s execution environment, turning a simple property injection into a full Remote Code Execution (RCE) chain.

The Mechanics of the Pollution

At its core, prototype pollution occurs when an application merges user-supplied objects into existing objects without proper validation. In JavaScript, every object has a hidden reference to its prototype, accessible via __proto__. If an attacker can inject a property into Object.prototype, that property becomes available to every object in the application.

Consider a standard merge function:

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

If the source object contains a __proto__ key, the merge function will traverse into the prototype of the target object. By injecting {"__proto__": {"polluted": "true"}}, an attacker can effectively set the polluted property on every object created thereafter. This is the entry point. The real danger, however, lies in what happens after the pollution.

Finding the Gadgets

Polluting the prototype is only half the battle. To achieve RCE, you need a "gadget"—a piece of code that consumes an object property in a dangerous way. The most common gadgets in Node.js involve functions that interact with the operating system, such as those in the child_process module.

When an application uses child_process.spawn or exec, it often accepts an options object. If that object is derived from a polluted prototype, the attacker can control critical settings. For example, if you can pollute the shell property, you can force the application to execute your commands through a malicious binary instead of the intended one.

The research presented at Black Hat 2023 identified several high-impact gadgets in widely used libraries. For instance, by polluting the NODE_OPTIONS environment variable, an attacker can force the Node.js runtime to load a malicious module or enable the remote debugger, as seen in various Parse Server vulnerabilities.

Practical Exploitation on Engagements

During a penetration test, your goal is to map the application’s data flow to find where user input reaches a merge or clone operation. Tools like Semgrep or CodeQL are essential for static analysis to find these sinks. Once you find a potential sink, you need to verify if it is reachable.

If you are performing dynamic analysis, use strace on Linux or Procmon on Windows to monitor the application’s system calls. If you see the application spawning child processes, look closely at the arguments and environment variables passed to them. If you can influence these via prototype pollution, you have a viable RCE path.

A common technique is to use a race condition. If the application is under heavy load, you can flood it with requests. One request triggers the prototype pollution, and a subsequent request triggers the vulnerable gadget before the application state is reset or the process restarts. This is particularly effective against server-side applications that maintain long-lived objects.

Defensive Strategies

Defending against these attacks requires a multi-layered approach. First, stop using objects as simple key-value stores if they are going to be merged with user input. Use Object.create(null) to create objects without a prototype, which effectively neutralizes __proto__ pollution.

Second, implement strict property filtering. Before merging any user-supplied object, ensure it does not contain dangerous keys like __proto__, constructor, or prototype. The OWASP documentation on Prototype Pollution provides excellent guidance on how to sanitize inputs effectively.

Finally, keep your dependencies updated. Many of the RCEs discovered in the last year, such as those affecting Kibana, were patched by simply updating the underlying libraries that handled JSON parsing or object merging.

Prototype pollution is no longer just a theoretical bug. It is a reliable, high-impact primitive that can bypass traditional security boundaries. When you are auditing Node.js applications, don't stop at the pollution. Look for the gadgets that turn that pollution into code execution. The next time you find a way to inject a property into an object, ask yourself: what happens if I control the environment of the next process this application spawns? That is where the real bugs are hidden.

Talk Type
research presentation
Difficulty
advanced
Category
web security
Has Demo Has Code Tool Released


Black Hat Asia 2023

45 talks · 2023
Browse conference →
Premium Security Audit

We break your app before they do.

Professional penetration testing and vulnerability assessments by the Kuboid Secure Layer team. Securing your infrastructure at every layer.

Get in Touch
Official Security Partner
kuboid.in