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 executea()
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.
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.