Intigriti’s December XSS Challenge 2020 (unintended solution)

GrumpinouT
InfoSec Write-ups
Published in
6 min readDec 13, 2020

--

As always, I started with reading the rules. The goal is to execute alert(document.domain) on the challenge-1220.intigriti.io domain. Self XSS and MiTM attacks are not in scope, and the solution should work on the latest version of Firefox and Chrome.

Challenge page
Challenge page

The first thing I did was using the calculator like it’s supposed to be used, to see how it reacts on my input. It looks like the first number, the operator and the second number of the calculation are set as parameters in the URL. It looks like we’re supposed to inject a payload in these parameters, so I started looking at the JavaScript, to see how it handles the parameters.

The code that is part of the solution, exists of three functions. init() , calc(num1="", num2="", operator="") and getQueryVariable(variable) .

Challenge code
Challenge code

The init function tries to execute calc, and gives it three arguments, retrieved from getQueryVariable. Calc will do some checks on the arguments, and if they are valid, it executes eval to calculate the operation.

GetQueryVariable, takes the part of the URL after the first question mark. It splits this string on ‘&’ and then again on ‘=’ to retrieve each parameter with it’s value. If the parameter name is equal to the variable argument, the value of this parameter is placed in the value variable. After iterating over the parameters, the value variable is returned. This means that in case of duplicates, the last parameter gets returned.

The first thing I noticed in this code was the eval(operation) on line 17. I know that this function is vulnerable to XSS, and the Mozilla page confirms this with a warning at the top of the page.

Mozilla warning that eval is vulnerable to arbitrary code execution
Mozilla warning

My idea was to somehow compile a payload in the operation variable, so it gets executed by the eval function. However, this doesn’t look easy. The payload has to consist of 3 parts, num1 , num2 and operator . Num1 can only contain numbers and letters (both lower- and uppercase) and ‘-’. Num2 can also contain only numbers and letters, but it cannot contain ‘-’. This looked very suspicious to me. Why is the regex on num1 different from the regex on num2? Is this a typo from the developer (@securinti on twitter)? I couldn’t find any possible payload that makes use of ‘-’ in num1. So I started looking at the rest of the code.

The operator had to be in the constant array operators. Here I noticed that operators contained ‘=’ but the calculator doesn’t have this button. From this, I concluded that I had to set the operator to ‘=’ (in the URL you have to specify it with ‘%3D’ otherwise the getQueryVariable() will split on the ‘=’ character, and the character itself won’t be passed to the calc function. At last, the length of the operation cannot be longer than 20 chars.

So what payload can we give eval()? We can assign variables! I tried the following search query

?num1=a&operator=%3D&num2=123

To check if it worked, I went to the console and typed a to see the content of the variable and indeed, this did work.

How can we use this knowledge to execute an alert? In JavaScript, it is possible to override functions. For example if we have function a

function a() {
console.log("a");
}

and function b

function b {
console.log("b");
}

Now if we execute a() we would see “a” in the console, and if we execute b() we would see “b”.

If we now execute the following a=b , and afterwards we execute a() , this time we will see “b” in the console. This is because we told the webpage to set function a equal to function b, so now if we execute function a, the code of function b gets executed. So now if we try the following search query

?num1=eval&operator=%3D&num2=alert

an alert gets popped after we press a button on the calculator. This is because we told the webpage that eval should be set equal to alert, and now instead of the original eval function, the alert function gets called. At this moment we can pop an alert, but we can’t pass document.domain to the alert because of the checks made earlier on the operation. This means we have to look for another function to override. I think the calc function is ideal, because this is the only other function that is part of the challenge where we can manipulate which arguments gets passed. Instead of overriding this function with alert, I override this function with eval, because the argument will be a string, and alert(“document.domain") will just say ‘document.domain’ in the alert instead of the desired domain. So now we have the following search query, but we still need to find a way to pass our code to the overridden function.

?num1=calc&operator=%3D&num2=eval

I was stuck on this part for a while, but one of the tips given on twitter said ‘hashoo’ with the following GIF.

GIF from Intigriti tip
GIF from Intigriti tip

From this tip, I concluded that I should work with a URL hash (#). After trying a lot of different things, I noticed that I can add some text before the ‘?’ and after the ‘/’ in the URL. The text has to start with ‘#’ so the browser won’t see the text as a filename, but as a hash. The URL I got at this point is:

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

This is the part where it gets tricky. To be honest, I do not understand why the browser is doing some of the behavior mentioned below, but I was pretty sure it could be exploited.

After going to this link, and pressing a number or operator button (not the clear button), I noticed that the #sometext? gets added to the num2 or operator parameter, depending on which button you pressed.

https://challenge-1220.intigriti.io/?num2=NaN#sometext?num1=calc&operator=%3D&num2=eval

The num1 parameter gets denied this time, because another question mark has been added before ?num1 . So now the JavaScript doesn’t handle it as num1, but as ?num1, because getQueryVariable only denies the first question mark in the URL. Now we don’t have our check anymore on the num1 variable in calc, and we have a way to make the first num1 invalid. The only thing left to do is add a new valid num1, so our eval (which was previously calc) will execute it.

Note: we have to do it with num1, because if we pass more than one argument to the eval function, only the first one gets executed (num1 in our case).

The way to add this new valid num1, is by adding it as the hash we had previously. after the alert, I added ‘;//’ so JS will handle everything else in num1 (‘?num1=calc’) as a comment. The final URL is:

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

When we now press a number on the calculator, alert(document.domain) gets executed and the URL looks like this:

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

This was my (unintended) way to solve the challenge, I am curious to see what the intended way was!

Feel free to contact me if you have any questions, and I will try to answer them as good as I can.

--

--