3 and a half solutions for Intigriti’s challenge 1220

holme
10 min readDec 14, 2020

Just show me to the solutions already!
Fair enough:
Solution #1
Solution #2 | No user-interaction
Solution #3 | An unintended solution
Solution #3.5 | A different approach to getting XSS

It’s December and this year Christmas came early! On 7/12, a new tweet ticked in from Intigriti announcing a new challenge:

Since Intigriti recently paid out €185.000 in bounties in one day they thought it would be a good idea to make us a calculator, but it seems like it can do a bit more than intended…

The rules

The solution to the challenge should meet the following requirements:

1. Should work on the latest version of Firefox or Chrome
2. Should execute the following JS:
alert(document.domain).
3. Should be executed on this domain (
challenge-1220.intigriti.io)
4. Shouldn’t be self-XSS or related to MiTM attacks

Let’s get started!

Navigating to https://challenge-1220.intigriti.io/ we’re met with a simple calculator that can do cool (but simple) calculations.

A leet calculation

When using the calculator we can notice that some GET parameters are being set:

Time to check the source code

This seems like a good time to take a look under the hood and examine how the calculator works. The page only uses a single script called script.js (the script can be found at the bottom of this article). Upon the page has loaded, a function called init is called. This function then uses a custom functioncalled getQueryVariable to get the parameters num1 , num2 and operator from the URL. Let’s examine exactly how getQueryVariable retrieves the parameters from the URL.

The function is passed the name of a GET parameter to return the value of, and performs the following steps:

  1. Save everything after the first occurrence of ‘?’ in location.href as the variable searchQueryString
  2. Split searchQueryString based on ‘&’ chars and save it in the vars array
  3. Split each element in vars based on the ‘=’ char and check if the first part matches the sought GET parameter
  4. If it matches, save the second part in the variable value
  5. Return value

This means that if a GET parameter is set more than once, only the last value is used. It also completely ignores the structure of the URL and looks at everything after the first occurrence of ‘?’.

After getting the values of the three parameters, they are passed along as arguments to the function calc. This function combines them into an operation and calculates it. Looking at the source code ofcalc immediately put a smile on my face seeing the use of eval :

As can be seen, the three parameters num1 , num2 and operator are combined with the use of template literals to a variable called operation and then thrown into eval! Well, that seems great, now let’s just set one of the parameters to some javascript payload and… wait. Seems like we went a bit too quick there. As can also be seen in the source code of calc , the parameters aren’t allowed to be everything and are validated in 3 steps, before being passed along to eval .

  1. A check is performed on the operator parameter to see if it matches it’s one of the predefined values from the array operators consisting of: ["+","-","/","*","="]
  2. Parameter num1 and num2 are both tested against a regex pattern.¹ The checks are almost equal: Both parameters must only consist of alphanumeric characters except the fact that num1 is also allowed to contain hyphens (‘-’)².
  3. A check is made to make sure the whole operation (all three parameters combined) doesn’t exceed a length of 20.

An initial thought

An interesting thing to note is that the operators array contains the equal operator, which means that we are allowed to set the operator parameter to an equal sign. This is interesting since we’re then able to do assignments, eg. setting the value of a variable.

Let’s test this! Imagine that we define the parameters as follows:num1=smth,operator== and num2=blabla. This would then be combined and thrown into eval as follows:

As shown, we’re able to do assignments! This seems very promising!

So what you can set a variable?

Well, it’s not just variables we can assign. In javascript it’s possible to override a function as follows:

thisFunction=thatFunction

Let’s try and do that with a function from script.js and see what happens. Let’s set the function calc to something else. What about alert?

https://challenge-1220.intigriti.io/?num1=calc&operator=%3D&num2=alert

Hmm, did it work? Let’s open up the developer tool and check the calc function:

Yay! We’ve successfully overridden calc with alert. Let’s click a random button on the calculator to call the calc function again:

https://challenge-1220.intigriti.io/?num1=calc&operator=%3D&num2=alert

Cool! We got a beautiful alert box. Since num1 is the first argument passed along to calc we are seeing the value ‘calc’ (remember that num1 is set to the value ‘calc’). Well, all we have to do now is to control the argument sent to alert! How hard can it be? Well, quite hard actually.

Approaching a solution

After discovering that I could override a function, I started to look for functions where I had control over the arguments passed along. I recalled that the function getQueryVariable ran through each GET parameter and passed both their name and value to the function decodeURIComponent. My first idea was to override decodeURIComponent and set it to alert and then have a parameter with either the name or value set to document.domain. However, there was one problem with this approach. To override decodeURIComponent and set it to alert, I would need to have the following operation evaluated:

decodeURIComponent=alert

This operation consists of 24 chars… Remember step 3 in the validation in calc? We can’t have our operation to be longer than 20 chars…

This caused me some problems for a while, since all the ideas I had, required operations longer than 20 chars.

Try harder

It’s time to try harder. Earlier we managed to pop an alert box with the value of num1 by overriding calc. If only we where able to change the value of num1 before calc is called again we could do something like this:

  1. Make the operation num1=alert
  2. Set the value of num1 to document.domain
  3. Call calc

The problem is that to change the num1 parameter, we would have to refresh the page. Right?

Found here

Let’s just not refresh

Remember how I mentioned that getQueryVariable looks at everything after the first occurrence of ‘?’. What if we created a URL fragment…

At this point, my eyes widen since I knew I was getting on the right track. The great thing about URL fragments is that we can modify them without needing to refresh the page. This is great in our case since it means we potentially modify the value of num1 without the need of refreshing!

Let’s test if we can actually set the parameters from the fragment of the URL:

https://challenge-1220.intigriti.io/#?num1=calc&operator=%3d&num2=alert

Sure enough, it works! We can now modify the fragment from the console with the following code:

location.hash = "?num1=document.domain"

And then click a random button to call calc again:

A moment of confusion

Eh, that was not the value I expected to see. So what went wrong? Notice how the URL looks after we clicked a button. When clicking the button the string “num1=NaN” was prepended to the fragment effectively setting the value of num1 to NaN#?num1. To fix this we need to add a ‘&’ before ‘num1’ to tell getQueryVariable that we’re defining a new GET parameter instead of specifying the value of the previous parameter. Let’s try with the following code instead:

location.hash = "?&num1=document.domain"
*facepalm*

Sooo yeah. Well, we managed to alert “document.domain” but we did it a tad too literally. Let’s try and use our brain and actually alert the value of document.domain instead of the string.

Eval to the rescue

We can simply fix our stupid problem by throwing in an extra eval. If we set calc to eval instead of alert, we can then simply pass along the argument “alert(document.domain)” instead of “document.domain”. Our steps would then be:

  1. Make the operation num1=eval
  2. Set the value of num1 to alert(document.domain)
  3. Call calc
That’s better

Finally, we managed to pop the correct alert! Time to automate this!

Solution #1

Let’s try and automate our approach. To do so we need to have our own script control the value of the fragment of https://challenge-1220.intigriti.io/. Let’s spicy things up, cross our fingers, and try and load the challenge in an iframe on our own page.

A moment of relief

Thank goodness we’re allowed to embed the page in an iframe.

Title

Let’s finally make some PoC code. Our code should do the following:

  1. Load the challenge page and set calc=eval
  2. Change the fragment to set num1=alert(document.domain)

Since our parent frame isn’t on the same domain as the challenge page, we run into CORS problems if we try to access location.hash of the challenge page. But luckily, we can instead just modify the src attribute of the iframe and as long as we only modify the fragment, the page won’t refresh.

The following code does the job:

And upon clicking a random button our XSS fires:

A beautiful moment

Voila!

Solution #2 | No user-interaction

But wouldn’t it be nice to have the XSS fire without user-interaction? Yes, it would. So let’s do that!

My thought process while working on this was simply as follows. We need to have multiple operations evaluated automatically. What could be used to trigger something? Well, isn’t that what event listeners are for? Let’s find an appropriate event. The ‘hashchange’ seems like a good fit for the job. Now let’s simply create an event listener for the ‘hashchange’ event by assigning onhashchange. To have the calc function be called with the GET parameters, the init function needs to be run so let’s make our event listener call that function!

Our steps should then be as follows:

  1. Set onhashchange=init
  2. Set calc=eval
  3. Set num1=alert(document.domain)

The following code does the above:

Even more beautiful!

Solution #3 | An unintended solution

This was actually the first solution I found. It was an unintended solution and a bit weird, but I thought I would include it. When initially looking at the getQueryVariable function, I thought about the possibility of tricking the function into returning one value for num1 before clicking a button on the calculator and then another value after. Investigating the functions, the clear function seemed interesting. The function was called when clicking the clear button on the calculator, and removed the three GET parameters num1 , num2 and operator. However, unlike getQueryVariable which happily looked for GET parameters in the fragment of the URL, clear only removed the actual GET parameters and not the values specified in the fragment. I really felt like this parsing difference between the two functions should be possible to exploit and after some time, I managed to do so.

The PoC-URL is as follows:

https://challenge-1220.intigriti.io/?num1=calc&operator=%3d&num2=eval&#?num1=alert(document.domain)

The exploit works because getQueryVariable splits everything after the first occurrence of ‘?’ in location.href into parameters based on the ‘&’ char. This means that when the URL above is initially processed, the last occurrence of “num1” will be viewed as a parameter with the name ?num1 which is not equal to the value “num1”. This results in the first occurrence of the parameter being used, which value is “calc” and makes the operation:

calc=eval

Then when the clear button is pressed, the actual GET parameters are removed, and since no GET parameters are left, the first occurrence of ‘?’ is also removed making the modified URL:

https://challenge-1220.intigriti.io/#?num1=alert(document.domain)

After the GET parameters are removed, the init function is called which will call the calc function which we have overrode with eval and the first argument will now be alert(document.domain) completing the exploit!

Solution #3.5 | A different approach to getting XSS

As a last note, I thought I would cover a slightly different approach which can be incorporated in both solution #1 and solution #2. The idea is to set the location parameter to an XSS payload using the javascript protocol. This can be achieved through the use of the variable searchQueryString which is set to the content proceeding the first occurrence of ‘?’ inlocation.href . However, we run into length problems again:

location=searchQueryString

The above is 26 chars, but we can use our fragment trick from solution #1 and solution #2 to perform multiple operations which allows us to simply use an alias:

  1. a=searchQueryString
  2. location=a

Finally, we just need to make sure that we insert our XSS payload in searchQueryString in such a way that it’ll fire when setting the location.

Here’s an example of how to incorporate this approach for solution #2:

That was a lot, thanks for reading along! I hope you found this interesting and maybe learned something new!

Until next time,

@holme_sec

[1] This tool is great for testing regular expressions: https://regexr.com/

[2] Well, thanks a lot for that red-herring Intigriti… ;)

Content of script.js:

--

--