A successful prototype pollution chained to a DOM XSS

Rachid.A
InfoSec Write-ups
Published in
7 min readApr 10, 2023

--

Source: somewhere on Twitter

I recently found a vulnerability that is a little less common and quite interesting in how it works.

Today I decided to share with you my last little discovery and to explain a little more in detail how prototype pollution work.

What is prototype pollution?

Definition from PortSwigger : Prototype pollution is a JavaScript vulnerability that enables an attacker to add arbitrary properties to global object prototypes, which may then be inherited by user-defined objects. Although prototype pollution is often unexploitable as a standalone vulnerability, it lets an attacker control properties of objects that would otherwise be inaccessible. If the application subsequently handles an attacker-controlled property in an unsafe way, this can potentially be chained with other vulnerabilities.

Source: PortSwigger

Therefore, we understand that prototype pollution is not -really- usable as such but needs to be chained to be. Before going into little more detail, it is necessary to briefly recall some basic JavaScript notions to understand how this vulnerability works.

Almost everything is an object in JS

An object — in JavaScript — is a collection of key-value pairs whose values can be of different types (string, number, boolean..). The — minimalist — syntax of an object is as follows :

let myObject = {
prop1: "A simple string",
prop2: 100,
prop3: true
}

There are two ways to access these different properties, the first is to use dot notation :

myObject.prop1   // -> will return "A simple string"

The second uses bracket notation :

myObject['prop2']   // -> will return 100

As we will see a little later, we will use one of these two syntaxes to pollute the prototype. Remember for the moment that almost everything in JS is an object and that each object is linked to another object — parent — called prototype from which it inherits the methods and properties.

Object-oriented paradigm

Javascript is a multi-paradigm programming language that includes functional, object-oriented, procedural, and prototype programming. In our case, it’s the object-oriented side that we’ll be interested in, it’s a pattern of programming — which, as the name suggests — uses objects and consists of classes objects, and prototypes.

We are not going to develop this vast subject here, but only seek to understand how inheritance and prototyping work in JS.

As explained on PortSwigger, whenever you reference a property of an object, the JavaScript engine first tries to access this directly on the object itself. If the object doesn’t have a matching property, the JavaScript engine looks for it on the object’s prototype instead.
-> What is meant here by “object’s prototype” is the parent object.

When you create a new object, JavaScript automatically assigns it to one of its built-in prototypes depending on the type of the newly created object :

The type of my object here is a string. When I try to access a property of my object via dot notation, the browser offers me -as we can see in the picture above- a whole bunch of methods/properties that I never created : toLowerCase, toUpperCase (…).
This is normal, JavaScript automatically assigned my object to the prototype String, so myObject inherited all its properties and methods.

It is possible to see what the prototype of an object is using the following syntax (on most browsers) :

The syntax for accessing a property is the same as for any object as seen previously. The String prototype itself inherits from another prototype, which itself inherits from another prototype, it is possible to move up the chain by simply chaining like this :

myObject.__proto__.__proto__.__proto__

The important point here is that this syntax serves as a “getter” and a “setter” at the same time: it allows access to properties but also to modify them.

So what if an attacker could modify a prototype’s property by overwriting the value of an existing property used in the application with another value? He would “pollutethe prototype!
But as previously explained this would not make it an immediately exploitable vulnerability.

Recipe for a successful pollution prototype

We will content ourselves here with a passage taken from PortSwigger, simple, concise, and explicit:

Prototype pollution vulnerabilities typically arise when a JavaScript function recursively merges an object containing user-controllable properties into an existing object, without first sanitizing the keys.

Successful exploitation of prototype pollution requires the following key components:

- A prototype pollution source — This is any input that enables you to poison prototype objects with arbitrary properties.

-
A sink — In other words, a JavaScript function or DOM element that enables arbitrary code execution.

-
An exploitable gadget — This is any property that is passed into a sink without proper filtering or sanitization.

Source: PortSwigger

Real case with my find

Now let’s move on to a real case with my find. As the program is private, I will voluntarily change some details.

By doing my research on www.example.com I found several vulnerabilities (XSS, Iframe injection..) letting me think that user inputs were not always correctly sanitized. After going around the different vectors that can lead — directly — to injections of HTML / JS code, I thought of checking if it was possible to carry out a pollution prototype via the URL, if possible, the policy -quite lax about filters- would surely allow me to do something interesting.

I was starting with something basic, but I was blocked by the WAF :

?__proto__.zhero=zhero

I made several attempts but nothing successful, especially through the constructor but the firewall systematically blocked my requests. Still, it’s worth trying out;

In case the input has not been recursively sanitized:

?__pro__proto__to__.zhero=zhero 

Via the constructor (being an object it also inherits a prototype) :

constructor[prototype][zhero]=zhero

Remember to test for each case, the two ways of accessing an object, (dot notation and bracket notation). This can especially make a difference after bypassing the WAF, depending on how the URL and its parameters are processed.

Later, while researching my target on web archive, I realized that many URLs contained the # character followed by useful information. This indicated that it was not just an anchor, and if the application used this character it must surely be taken into account by the function which manages the URL and its parameters. So I was trying :

#__proto__[zhero]=zhero

The request passes, with no problem with the WAF, small test in the console to see if the prototype has been polluted :

Encouraging result, the prototype has been polluted. After some research in the JS code, the responsible function is this one :

As you can see, absolutely no filters are in place. The input could still have been passed through filters before being passed to the function, but that was not the case here.

As explained earlier, this is not enough for the recipe to be successful. It is now necessary to inject a payload into a property used dangerously by the application. Looking at the source code, the key-value pair previously injected via the URL is used as an “attribute-value” in a style tag :

After seeing this, there was no mystery on how to exploit, just use an Event handler as an attribute and some JS code as a value :

#__proto__[onload]=alert(%22XSS by zhero_%22)

We were able to chain our pollution prototype to an XSS. It is of course possible to inject any more elaborate JS code. The native alert function has been used — as usual — for example purposes only.

The entire application was vulnerable, so it was possible to use this payload on any part of www.example.com.

Thank you for reading me, if you have any questions do not hesitate to let me know. Happy hunting 🏹

Take a look at my previous write-up titled : Business logic flaw, the enemy of scanners

https://medium.com/bugbountywriteup/business-logic-flaw-the-enemy-of-scanners-45e96304f55f

--

--