Published: 22 June 2022 at 13:17 UTC
Updated: 22 June 2022 at 13:19 UTC
We recently launched a new version of DOM Invader that can find Client-Side Prototype Pollution (CSPP).
If you're not already familiar with Client-Side Prototype Pollution, check out the post above. Just to recap, a successful CSPP exploit requires two components:
In this post we're going to talk about CSPP gadgets in browser APIs and how we found them in common libraries too.
I was quite surprised to discover that some JavaScript APIs in the browser contain prototype pollution gadgets. Functions that accept objects as arguments can be polluted just like any other object. The fetch function is one such example. When calling fetch, there is an optional argument that accepts an object - this allows you to control headers, body parameters, etc. If a site doesn't specify one of those properties, then it's possible to use prototype pollution to control them provided there is a prototype pollution source:
Object.prototype.body = "foo=bar";
fetch('/', {method:"POST"})
Even ES5 functions such as Object.defineProperty
are vulnerable - if a developer does not specify a "value" property, then prototype pollution sources can be used to overwrite properties! Consider the following example:
let myObject = {property:"Existing property value"};
Object.defineProperty(myObject,'property', {configurable:false,writable:false} );
myObject.property = 'Should fail';
alert(myObject.property);//Existing property value
If you use prototype pollution on the value property, then you can overwrite "property" even though it's been configured as not writable:
Object.prototype.value='overwritten';
let myObject = {property: "Existing property value"};
Object.defineProperty(myObject,'property', {configurable:false,writable:false});
alert(myObject.property);//overwritten!
So even though the property has been made unconfigurable and unwritable, by using a prototype pollution source we can poison the descriptor used by Object.defineProperty
to overwrite the property value. This is because if you don't specify a "value" property on the descriptor then the JavaScript engine uses the Object.prototype
.
Whilst testing various bug bounty sites, DOM Invader was reporting a gadget called "hitCallback
" that was sent to a "setTimeout
" sink. We traced this back to Google Analytics using the setTimeout
sink, and discovered that "c
" contains the value from the prototype pollution gadget:
this.Ja = function() {
!b.fb && c && setTimeout(c, 10)
}
DOM Invader shows the "hitCallback
" gadget inside the "setTimeout
" sink:
So for any website that uses this version of Google Analytics, and has a client-side prototype pollution source, it would be possible to use this gadget to gain DOM XSS on the target website. We successfully exploited this gadget on a well known game site, and others.
We've seen many websites, using Google tag manager, have a resultant vulnerable gadget "sequence
" that ends up in an eval
sink. There is also another gadget called "event_callback
" which ends up in a "setTimeout
" sink. We reported these to Google but they claim it's the customer's responsibility to ship code that doesn't contain prototype pollution sources. Personally, I think they should also fix gadgets where possible as a defence in depth measure. Quite hilariously, our own Web Security Academy site has one of these gadgets but thankfully no prototype pollution source. We successfully exploited this gadget on a Wordpress domain which has now been fixed as well as a few other sites.
The Wordpress exploit looked like this:
https://es.wordpress.org/patterns/?__proto__%5Bsequence%5D=alert%28document.domain%29-
The trailing "-" is required because the value ends up in a JavaScript expression alongside an integer.
Sergey Bobrov did an excellent job of documenting various CSPP vulnerabilities. On the Github page, I noticed Adobe's dynamic tag management scripts and decided to scan them for gadgets. DOM Invader found multiple undocumented gadgets, as well as the existing documented ones. The gadget "cspNonce
" was being used in an innerHTML
sink, as well as "bodyHiddenStyle
", this hit an innerHTML
sink too but has an existing <style>
block before the value was written.
There were various other gadgets in the context of script.src where you didn't have full control over the URL, but DOM Invader highlighted an interesting gadget. In the "trackingServerSecure
" gadget it found, you couldn't control the protocol but could control the host - this could lead to DOM XSS:
When using browser APIs that allow objects in arguments, care must be taken to ensure that you don't expose gadgets that can be later exploited by CSPP. A good defence in depth measure is to use objects with a null prototype when using these APIs. If you're writing or using a JavaScript library, it would be a good idea to scan it for gadgets before deploying it.