Roie Marciano
Penetration Tester @ Cilynx
Prototype Pollution Attacks: Unmasking a JavaScript Vulnerability
Prototype pollution attacks refer to a situation where an attacker can manipulate the prototype property of JavaScript objects. As a result, the attacker can affect all instances of the affected prototype chain, adding or modifying malicious properties in a way that might negatively affect the client side or the server side of the application.
This attack vector is often combined with other vulnerabilities. While attackers can modify or add properties to the prototype of an object, this alone does not always result in significant effects, such as executing code on the server or altering values on the frontend. In most cases, attackers will need to chain another vulnerability to exploit prototype pollution. On the client side, exploitation of prototype pollution typically leads to DOM-based XSS attacks, allowing attackers to execute malicious scripts within the user’s browser.
What is prototype?
Prototypes are a set of properties and methods that JavaScript objects inherit from their prototype chain (Important note – JS objects refer to all entities in JS e.g., array, objects, strings, or integers, not only the literal JS object).
For example:
let string1 = new String(“First name”);
This object inherits the “String.prototype” properties and methods that can be used on the String object. For example, the concat function:
let string2 = new String(“Last name”);
let string3 = String1.concat(“ “, String2);
String3 = “First name Last name”;
Note that primitive values do not inherit from the prototype chain. This means that a regular string primitive will not be affected by prototype pollution. Only when a string is used as an object does it interact with prototypes.
Let String = “string” – does not inherit from the prototype chain.
Let String = new String (“string”) – Inherits from the prototype chain.
Real-World Prototype Pollution to DOM-based XSS Attack
During my last penetration test, I successfully exploited a prototype pollution vulnerability that led to a DOM-based XSS. I discovered that by delivering a malicious link to the application’s users, I was able to steal the users’ cookies and identity.
How did I discover the prototype pollution vulnerability?
As a penetration tester, I know that the testing process can often unfold in unexpected ways, and that’s exactly what happened this time. My initial focus was on a JavaScript library with known vulnerabilities. I began by testing this library for a prototype pollution vulnerability, recognizing the potential to lead to this specific type of security issue.
The funny thing is that the actual vulnerability did not arise from the recognized library but rather from an unrecognized, other dependency!
I was testing different payloads, attempting to pollute widely used objects. After a few tests, I tried the “constructor.prototype” object, which is the prototype of the constructor object. This object is usually used to create other objects.
Using the next payload “/?constructor[prototype][key]=Polluted” and checking the value of “key” using the console log, I was able to confirm that the application was indeed vulnerable to prototype pollution.
But this is only the first part of the journey 😉, now I needed to find a gadget that affects the application.
DOM Invader
For this purpose, I used the DOM invader Burp browser extension to locate gadgets that could be affected by the polluted prototype object.
After the scan, the DOM invader located the exports and the public path gadgets that inherited the malicious key. Checking the DOM, I found that the pollution affected the JavaScript file sources. I couldn’t ask for a better injection point!!
Exploitation
The application used the ‘publicPath’ property to load JavaScript files. By polluting this gadget and on account of the CSP header that was not implemented, I could manipulate the browser of victims who click on a malicious link (with the intention to load different JS files).
I used an S3 bucket to serve the next malicious file (This file only contained a classic payload that sends the document.cookie to a domain within my control).
When the victims click on the malicious link it affects the browser DOM as described in the screenshot below.
After the application loaded the malicious script instead of the intended JS file, the victim’s cookies were successfully sent to a server under my control.
Impact
By delivering a malicious link that contains the prototype pollution payload, I could trick users into visiting a URL that would trigger the attack.
This attack was conducted from an unauthenticated perspective! And since the application server returns cookies without the “HTTPOnly” flag, the victim’s cookies could be sent to a remote server within my control.
Conclusion
By exploiting a prototype pollution vulnerability, I was able to trigger a DOM-based XSS attack, resulting in cookie theft.
This serves as a reminder to developers to practice secure coding and always to keep all components updated to prevent critical vulnerabilities like this prototype pollution, which we are slowly seeing more of.
As an additional security measure, implement relevant security headers, such as Content Security Policy (CSP). CSP is critical for preventing attackers who are attempting to execute malicious JavaScript from unsafe sources. Additionally, return cookies with the HTTPOnly security flag, which prevents JavaScript code from interfering with the application’s cookies. The HTTPOnly header can minimize the damage incurred if an attack like this comes to pass.