Writeup - AdBlocker MaltaCtf2025 Web Challenge
AdBlocker Web Challenge Writeup
Challenge: web/AdBlocker
Author: Dreyand
I’ve decided to play MaltaCtf2025, knowing that my friend DreyAnd made a web Challenge called AdBlocker. I’ve dedicated my whole saturday on this challenge.
My solution was unintended and I am guessing most of the people who had solved the challenge solved it this way. Unfortunately, I have not succeeded to solve the challenge even tho I had the solution for it. Probably because I was already 10hrs+ in this challenge AND it was 2am, believe it or not I have failed to escape a string properly in my payload which resulted to the payload not working. :) :) :).
I usually don’t lock in for challenges this much but considering my friend made it I was determined on solving it :( . Either way I hope you enjoy the writeup and I will try to go as indepth as I can and I will write my thinking process while doing this challenge.
tldr solution;
Using grandparent iframe trick to bypass SOP and set the grandchilds iframe location to our own url. Used race conditioning to bypass iframe removal then used about:blank as a url for iframe and then SOP allows us to just do .document.write and write our own stuff on the blank website. Challenge used post messages for communication which I abused via xss.
Solution
First look at the challenge setup and code
The challenge setup had a /web and /analytics directory and two websites http://web:1337 and http://analytics:3000 . Web directory had a source code for the http://web:1337 site and a bot.js source code which immediately rings a bell XSS. Looking at the bots code it sets a cookie on analytics website which probably means we have to steal the cookie via xss. The cookie is not httpOnly, secure is set to false and sameSite is lax which means the cookie is stealable.
I usually first look at where the flag is located to get an idea of what are the ways we can retreive it, for e.g if its set in flag.txt and not called anywhere in the app we prob need to have some kind of arbitrary file read, RCE etc… In this case xss was the way.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies
bot.js
1 | |
Main web app source:
ad.html
1 | |
the code is pretty clear and readable so I wont go into details of it. It listens for the post messages and runs a function that creates a frame, and calls ad.js after 1ms.
ad.js
1 | |
The code deletes the iframe if it exists and sends a fetch req to /ping on analytics website.
Straight away I had an idea doing a grandparent iframe (will elaborate later on) trick which I read about on some chineese blog months ago. Suprised I remembered of it straight away tbh :) .
Analytics directory included source code for analytics website.
integrate.html
1 | |
We can see here that the code also has a post message listener, and if there is a check that sends a postMessage to the parent.
Whats interesting is the message listener, if it passes all checks it sends a request to a url provided via post message and cookie with it. Which is probably the way of requiring the flag at the end.
My idea for the solution was following:
Do the grandparent trick (which changes the url of the iframe inside of web:1337 (explained later on)) -> send a post message from our controled domain iframe to the web:1337, bam xss the rest is easy?
I won’t be providing the backend source code for either web apps because it is not related to the solution (at least in my case)
Exploitation
Without further thinking, I went straight ahead into trying the grandparent trick with iframes.
The grandparent trick is the trick where the SOP allows you from an attacker site to change the location of the grandchild iframe inside of a websites iframe.
Setup example:
1 | |
After trying this trick for some time I could not get it:
1 | |
With errors:
1 | |
I did not get why, after some time i realised that the iframe was deleted by ad.js lol and that made it unaccesable. I did not know this error shows up when the trying to change the value of unexisting iframe…
Now I figured I have to “catch the iframe”, it was loading ad.js after “1ms” supposably. But looking at the network section in devtools I saw there is around 30+ms delay between when the website is loaded and ad.js is loaded. Race time 🏎️.
Well, turns out I got stuck on this for… 4 hours 💀.
The issue I had is that I catch the iframe, but the request to my url is canceled everytime. While banging my head for 4 hours just on this I was about to give up tbh… Turns out this happens bcs I was spamming the url, even after it was set. I had no idea browser cancels requests when they are being spammed this way…
Current code:
1 | |

So after spending 4 hours trying to fix this and looking at the network tab and ms I thought myb the ad.js bcs it was async await call it cancels the request or something… No idea :| .
Tbh I still have no idea why this was happening, My guess is something to do with this or the async await thing I mentioned above:
https://medium.com/@itskishankumar98/api-cancellation-on-the-browser-5504bb68c974
I suddenly remembered one other way that I’ve read about to bypass the frame-src CSP. Well even tho there was no CSP in this case I thought well, what if I don’t need to fetch and request anything? What if I can load a blank page and just write on it?
Yup!
I immediately tried using about:blank instead of an url and SOP allows you to write on about:blank page by default so I just loaded about:blank and wrote my own html on it == works!
1 | |

Now the only thing we’ve got left is:
Xss on web:1337 sends a post message to the listener on analytics:3000, which passes all the origin checks and I put my webhook url inside of the url of the post message. Well I was right…
Well considering I’m dumb and inexipirienced I tried opening the analytics:3000 with iframe then sending postmsg that way, turns out that iframes dont load cookie? :P after being stuck on this too it was alr like 12-1am ?
I figured ok yeah I’m dumb I will just use window right? Well yeah :|
After trying with window for 1hr+ It DID NOT WORK. Turns out it was a correct solution by my dumbass didn’t escape all the strings properly… I was really tired so my brain was barely working atp. I concluded okay idk why but maybe bot needs to click on this bcs browser is blocking it?
My payload :_) ; Turns out I already hade a good solution but that small string esc was tearing me up :), even tho for some reason locally it worked but eh… Again the inexpirience kicked in, instead of making a variable with payload then just using it in the document.write part I was writing the payload inside of the html which prob created all those problems in the first place…
My 2am notes:
Turns out I was supposed to use \\\ instead of only \ to escape a single quote ' … For some reason it worked for me locally but ig the error was in the post message to analytics:3000 part which I didn’t test bcs of the origin checks…
Final payload (prettified):
1 | |
flag=maltactf{th1s_w4s_4s_exh4ust1V3_aS_th3_C0nnection_P00l}
Great chall!