As always, I started with reading the rules. The goal is to alert()
the following flag: {THIS_IS_THE_FLAG}
. Hmm.. this is new! With the previous challenges I’ve solved the goal was to execute document.domain
. The solution should leverage a cross site scripting vulnerability on this page, shouldn’t be self-xss or related to MiTM attacks, and should work on the latest version of Firefox and Chrome.
After reading the rules, I would normally use the webpage like it’s intended to be used but at first sight, there doesn’t seem to be any functionality, so I immediately took a dive into the source code.
There it was, line 2 of script.js
. the code seems to handle the r
search parameter!
2| window.r = href.searchParams.get("r");
Okay so now that we know we could inject via this parameter, let’s see what else is present in script.js
.
At first the forEach()
function seemed a little bit complicated to understand, but thanks to the comment line and some debugging, it’s easy to actually understand it. I put a breakpoint on line 7 and refreshed the page to see what the content of interface
and globalVariable
would be. It turns out this function loops over every property of document and window. If the property type is a string, and it contains the string javascript
, the property will be deleted.
We can deny the warning in the code, I quickly googled this but it doesn’t seem to be a problem for this challenge
Okay so if we set the searchParameter r
to something that contains the string javascript
, It will just be deleted. and the console will display an error that r
is not defined.
https://challenge-0121.intigriti.io/?r=javascript
Okay on to the next part of the code!
Here we see that the default action of every URL (a
tag) on the page is overwritten by a function called safeRedirect()
. To this function the argument e.target.href
is passed, which is just the content of the href attribute of the anchor tag.
We can also see that if r
, remember this is our search parameter, is undefined, the safeRedirect()
function is called with argument r
.
So what is this safeRedirect()
function?
First of all we see that the search parameter r
is meant to be a URL/path (the parameter is literally called url
). Next we can see that the parameter shouldn’t contain any of the following characters: <
, >
, "
, '
and a space.
If the URL (r
parameter) starts with https://
, window.location
will be set to the URL, which redirects the user to any site specified. Yay, open redirect! Else the window.location
will be set to window.origin + "/" + url
.
This redirect will happen after 5 seconds (5000 milliseconds). If the redirect didn’t happen within the 5 seconds, an error message will be displayed, with an a
tag so the user could click on the URL.
Okay, at this point there are a few things that might be in our way of solving this solution. I noted them down and started to search a bypass for them one by one. I started with only two problems and added problems to the list later on.
List of problems:1. r variable can't contain "javascript" string2. r variable can't contain <, >, ", ' or a space3. inject in a tag of error message4. Don't make the browser redirect so that the error message will be displayed as a block5. the URL will always start with https:// since window.origin is https://challenge-0121.intigriti.io
The first two problems are fairly easy to bypass.
The check on the javascript
string is case sensitive, so we can just pass Javascript
, JaVaScRiPt
, etc.. to this variable and it won’t get deleted.
The second problem is that we can’t inject any html since <
and >
can’t be used in our input. We still got our a
tag where we could inject malicious code, this would probably be the way to go! One thing to note here is that we should alert()
the following flag: {THIS_IS_THE_FLAG}
. this flag should be passed as a string, but "
and '
can’t be used for this. Luckily we have other characters that are allowed, like a backtick `
. If you didn’t know that you could use a backtick for this, just have a proper look at script.js and you will see that they are used around the error message.
The third problem is a little harder. To inject in the a tag, you would need whitespace to escape the href
attribute, and as we previously found out, we could not use a space. To bypass this, I had a look at the ascii table. I tried the first few hex codes, one by one, and found one that acted as a space while injecting it. The hex code I used was %0a
which acts as a new line, but will be displayed as a normal whitespace in the html. The important thing here is that if we want to escape the href
attribute, we would need to add something like a character b
before our first %0a
, otherwise the browser would render it like this: <a href="onclick=alert(1)">here</a>
. It would ignore the %0a
and see everything after it as href
.
Now I was able to inject in the a
tag, but things like onclick
and onmouseover
didn’t work because display
was set to none
, and there is no way to override this. For this, we need to solve problem 4.
One of the first things I tried was passing a%00a in the URL in the hope that it wouldn’t redirect me because of the 0 byte, and indeed, it didn’t. But here the problem was that it gave a DOM exception and prevented the other JavaScript from running…
I started doing some research on how to prevent the browser to redirect and quickly found the google Chrome setting privacy and security
> Site Settings
> Content
> Pop-ups and redirects
. This was set to Blocked
but for some reason it allowed the redirect on the challenge page. I don’t understand which redirects this setting blocks and which it doesn’t, but I started searching sites that Google Transparency Report marked as unsafe. To my surprise, these sites are harder to find then I thought, since most of the ones I found weren’t active anymore. After some time of googling, I found the following URL:
https://vehicle-tax-renewal.co.uk/
I tried this URL to redirect to, but it turned out Chrome doesn’t block redirecting to this URL, it just displays a warning message on the URL to notify the user the site is malicious.
We don’t have a solution for this problem so far, but we will come back to it a little later.
The fifth problem is probably the hardest. Once this one is solved, we will also be able to solve problem 4. To solve problem 5, let’s take a look at the first tip:
Hmm, the scope… Oh wait a second. Remember the rules? One of them was the following:
Should leverage a cross site scripting vulnerability on this page.
As far as I could remember the rules of the last two challenges were a little different, but I wasn’t sure. To check this, I went back to one of my older writeups and there the rule was:
Should be executed on this domain (challenge.intigriti.io)
There is a clear difference here!
To confirm this I also looked at the scope of the challenge program on Intigriti. The following domain was in scope: *.challenge-0121.intigriti.io
. I don’t know if this was also the case with previous challenges, but I think it’s safe to say that we can conclude we should do something with a subdomain.
At this point, I might have or might have not used Sublist3r to find subdomains (of course without any result). So what can we do that includes a subdomain? I tried using a
as a subdomain and it turns out we just stay on the challenge page!
Now we can go back to the code that deletes any string property of document and window that contains javascript
. Let’s put a breakpoint on line 8 (starts with delete
) and enter the subdomain javascript
to see what window properties are deleted. It turns out only window.origin
is deleted. Now if we try the following URL
https://javascript.challenge-0121.intigriti.io/?r=1
we will be redirected to to
https://javascript.challenge-0121.intigriti.io/undefined/1
With this, we are on our way to solve problem 5, but let us first go back to our list of problems for a second.
List of problems:1. r variable can't contain "javascript" string
--> use capital letters (Javascript)2. r variable can't contain <, >, ", ' or a space
--> use backticks (`)3. inject in a tag of error message
--> use %0a instead of a space4. Don't make the browser redirect so that the error message will be displayed as a block
--> TODO5. the URL will always start with https:// since window.origin is https://challenge-0121.intigriti.io
--> use subdomain javascript
--> TODO: inject in window.origin
Find a way to inject in window.origin… I’m sure this would be the solution, but I’m not sure how I would be able to do this. I started doing research on doing this but there isn’t any proper answer for this except from that it is not possible because the challenge page is cross origin. At this point I was clueless and decided to have another look at the tips on twitter.
Bring ID, hmm time to go back to problem 3, I think we need to inject an id here. But what id do we need? I tried the two id’s that are used in the JavaScript code (popover
and error
) but it looks like these id’s are not useful to solve the challenge…
After some trying, I noticed that window.error
returned the html tag with id error
. This looks very promising! what if I inject the a
tag with id=origin
?
Note: This would not have worked if we didn’t delete window.origin.
At this point I got the following URL:
https://javascript.challenge-0121.intigriti.io/?r=b%0aid=origin
This redirects to
https://javascript.challenge-0121.intigriti.io/b/aid=origin
As you can see the browser removed the %0a
in our payload, but also the /b/
points to the character we added before the first %0a
. Now I replace this character in the URL with Javascript:alert(1);
(Keep in mind the capital letter). Our full URL would be:
https://javascript.challenge-0121.intigriti.io/?r=Javascript:alert(1);%0aid=origin
With this payload I expected the page to redirect to Javascript:alert(1);
but it looks like that wasn’t the case. Instead, I just solved our problem 3. As we can see in the console, there is an unexpected token ‘:’.
It turns out we don’t need the Javascript:alert(1)
, but we can replace it with anything as long as it has the following structure: string:random-chars
.
Note:
Javascript:alert(1)
would have worked if you put//
behind it. Example:
https://javascript.challenge-0121.intigriti.io/?r=Javascript:alert(1)//%0aid=origin
Since the error is now displayed as a block, we could inject it with things like onmouseover=alert(1)
. I though this was the unintended solution because hovering the mouse over the URL is still user interaction, but then I came with the idea to add a very large padding.
Now hovering the mouse anywhere over the screen result in the execution of the alert!
When we now come back to our list of problems, we see that we actually didn’t need to solve problem 1, the check for the string javascript
was only there so we could delete window.origin
. Then we used the solution of problem 2 to pass the flag string to the alert, the solution of problem 3 to inject in the a
tag and the solution of problems 4 and 5 to inject into window.origin
and prevent redirecting.
https://javascript.challenge-0121.intigriti.io/?r=a:b;%0aid=origin%0aonmouseover=alert(`{THIS_IS_THE_FLAG}`)%0astyle=padding:800000px;
https://javascript.challenge-0121.intigriti.io/?r=Javascript:alert(`{THIS_IS_THE_FLAG}`)//%0aid=origin