- Critical Thinking - Bug Bounty Podcast
- Posts
- [HackerNotes Ep.112] Interview with Ciarán Cotter (MonkeHack) Critical Lab Researcher and Full-time Hunter
[HackerNotes Ep.112] Interview with Ciarán Cotter (MonkeHack) Critical Lab Researcher and Full-time Hunter
We've got Monke breaking down some of his cool bug chains, some fresh WebSockets research ideas, a bunch of AI news, prompt injection research and some newsletters. Check it out below.
Hacker TL;DR
Bring a bug - Dirty OAuth Paths: Double URL encode the # when looking at applicable OAuth flows to ensure the code is never consumed and stays in the URL. This can be abused as part of a nice chain when attempting account takeover (ATO) primitives. Monke’s bugs below chain this behaviour in a really nice chain - be sure to check them out.
WebSockets Research: Some initial research by Monke on WebSockets revealed some pretty weird behaviour - by sending a message body on a WebSockets handshake:
Body gets misread as first WebSocket frame → unusual but not directly catastrophic.
Server responds with chunked encoding header → potential smuggling vector.
Combine the two → theoretical but juicy possibility for a WebSocket-based request smuggling attack.
Newsletters: If you’re looking for some good quality content from two great bug bounty hunters, check out the newsletters below:
Msty: A neat wrapper to prompt all LLMs with the same prompt and see all answers simultaneously, instead of having to ask each separately. Pretty neat. https://msty.app/
Prompt Injection: Some cool research by Embracethered covers some pretty sweet things with prompt injection. The research shows you can sometimes poke holes right through guardrails at the operator-level and can be abused to hijack ChatGPTs operator: https://embracethered.com/blog/posts/2025/chatgpt-operator-prompt-injection-exploits/
- Bring a Bug
A postMessage-based XSS in a targets chat widget. It's a true Monke-style bug right here.
In this particular bug on a Bugcrowd target, the target was using a Salesforce chat widget. By exploiting a postMessage vulnerability within the Chat Widget, it was possible to leak an OAuth code from the parent frame’s URL (the parent href
). Once you’ve got that OAuth code in your crosshairs, you can imagine how quickly a bug can escalate.
It might sound straight forward but it required quite a few gadgets and neat techniques. Let’s dive into this bug.
Dirty-OAuth Path & Double-URL-Encoded Hash
A key piece of the puzzle was a “dirty-oauth” path. It took advantage of a double-URL-encoded hash to ensure the OAuth code wasn’t properly consumed. If you manage to force the OAuth flow into a state whereby the code isn’t consumed, this code can be exchanged for session material.
Essentially, he abused a double URL encoded hash to ensure the code was never consumed and ended up in a flow where the code got stuck in a weird, half-recognized state that the app wasn’t expecting. This meant the code remained in the URL and unused, proving to be a useful gadget.
Fixating State to Bypass COOP
Now the next gadget. Usually, the state
parameter is used to protect against CSRF-based vectors. This is sometimes tied to the code
to ensure it is unusable outside of that context.
Turns out the state parameter wasn’t actually tied to the OAuth code. This meant he could fixate the state so that it pointed us directly to the second part of the OAuth flow. Why’s that important? Well, the first part had strict COOP (Cross-Origin-Opener-Policy) headers, but the second part… not so much. That mismatch in defences let him jump right around the COOP barriers, and luckily avoid them altogether.
A quick note on this: it’s always worth checking across all endpoints and all status codes if you’re up against some header-based protections. More often than not, there will be a place where the configuration is different, and that can be your in to craft a bug.
Landing the OAuth Code in a Salesforce Chat Widget
Next stop: landing the OAuth code in the URL of a page that loaded the Salesforce Chat Widget. Once it was there, the widget was vulnerable to a postMessage XSS, courtesy of an origin check that was a bit too trusting (turns out if you can host custom JS on a Salesforce-owned domain, you’re automatically “trusted”).
By sending a crafted postMessage to the Chat Widget, he could get it to render an iframe
with a javascript:
URL. That, my friends, is basically an engraved invitation to pop XSS within the chat widget’s context.
iFrame-Hopping for Fun & Profit
The chain went further: you could use window.open
references and iframes to hop contexts and eventually hijack the logging frame. That logging frame had all the juicy bits, including the OAuth code. With the XSS in place, you could leak that code to your own domain.
Quite the chain and to me, it highlights the importance of keeping track of those smaller gadgets. Chained together, they can make for some pretty tasty bugs.
Why This Is Cool
Session Fixation: Mismatched COOP headers + state fixation = sneaky way to bypass COOP headers in the flow.
postMessage XSS: Forever an impactful bug - especially when it leads to snatching up tokens.
Salesforce Domain trust: Knowing a target, or a third party of a target, can let you bypass checks in ways that probably weren’t considered. Leveraging custom JS hosting on a “trusted” domain was a big part of this bug.
iframe Hopping: Knowing what tools you can use to grab a reference to another frame can be incredibly useful in these kinds of chains.
The full chain for this one can be seen below:
window.open to fixated state → redirect with double URL hash fragment → code parameter will be unused in the URL hash → postMessage to SalesForce widget → iframe hop 1st to second frame (same origin) → hijack the frame → exfiltrate the code.

- Bring a Bug - Part 2
This one was pretty cool. An unauth’d ATO isn’t something you come across often, and this one used a CSTI (Client Side Template Injection) as the injection point. If you aren’t familiar with CSTI, the impact is usually XSS.
The target had a mirror domain the same as the CSTI page. There was a postMessage listener that would send the user’s credentials to the frame’s parent. Unfortunately, frame-ancestors were applied, meaning if the origin wasn’t specified you couldn’t perform the postMessage.
Another problem was if you received the cookies in an unauthenticated scenario, the page would log the user in and redirect and kill the rest of the chain.
To get past this, example.com
framed example.com
via the CSTI (which allowed XSS) and Monke purged the DOM. In that, he put an iframe with the other page that had logged in but was a different domain.
This met the frame-ancestors restrictions, and now the DOM was purged, the redirect no longer happened which was one of the pieces that originally killed the rest of the attack chain.
The complete chain for this one can be broken down into:
Unauth CSTI
Another page uses same cookies and has a gadget that sends the cookies to the parent, but it has a frame-ancestors directive
Use the CSTI to iframe itself
Purge the page and add a listener (stops the redirect from happening, and still allows communication as normal)
Iframe the other, different logged-in page and trigger the postmessage to send cookies to parent, and then exfiltrate

- WebSockets Research
If you aren’t familiar with WebSockets, here’s a quick TL;DR before we jump in:
They are their own protocol
They initially use HTTP to negotiate a connection, and ‘upgrade’ to the
WS
protocolThey’re asynchronous, meaning the 1 request → 1 response we’re used to seeing goes out of the window
This is roughly what it looks like if you’re looking at the underlying traffic:

So, Monke decided to poke around with WebSockets by sending a body during the connection request. Normally, you’d expect WebSockets to upgrade after a standard HTTP handshake, right? Well, in this case, the server took the body of that handshake and treated it like the first WebSocket frame. That means the server started interpreting the data like it was raw binary content.
But here’s where it got more interesting: the server responded with Transfer-Encoding: chunked
. Chunked encoding is often one of those prime ingredients in HTTP request smuggling attacks. Throw in a mismatch between Content-Length
and Transfer-Encoding
, and it can sometimes create a smuggling scenario where you effectively wedge extra requests into the connection.
So the theory is that you could combine:
A content length in your handshake request and a request body, and
The server returns a chunked response for the WebSocket upgrade,
…and maybe you manage to slip a “smuggled request” in the handshake or the frames that follow.
Is it definitely exploitable? Hard to say without deeper testing. But from a bug hunter’s perspective, once you see chunked encoding plus a weirdly flexible handshake, it’s definitely time to start fiddling around. This might be a new angle on the classic request smuggling, just in the context of WebSockets.
In short:
Body gets misread as first WebSocket frame → unusual but not directly catastrophic.
Server uses chunked encoding → potential smuggling vector.
Combine the two → theoretical but juicy possibility for a WebSocket-based request smuggling attack.
Keep an eye on these quirky handshake flows. If the server’s mixing up how it parses that initial request and uses chunked responses, you never know what might slip through the cracks.
If you’re a fan of newsletters, the guys dropped some pretty good suggestions on the pod. Douglas Day (archangel) recently went into full-time hunting and has decided to drop some great content on the side with his newsletter, and of course, MonkeHacks is still delivering consistent, great content.
If you’re looking for some more high-quality reads, check them out below:
- Grok3
If you’re a security researcher and you’re looking for the best in class, Grok3 might be the LLM you’re looking for.
With little/no persuasion needed, you can pretty much straight up ask anything - payload generation, mutations.. and it’ll generate it for you:

A tip from Rez0 on the pod, if you wanna pre-prompt and use a few models at once to test stuff out, you can check out msty:
You can prompt all LLMs with the same prompt and see all answers simultaneously, instead of having to ask each separately. Pretty neat.
One cool thing Monke dropped on the pod was using modified extensions and augmenting them with LLM capability. For example, Fran’s postMessage tracker, augmented with LLM to do a quick check on postMessage listeners, logic and any easy to reach sinks can be a quick win.
- From Day Zero to Zero Day
If you’re looking for a start into some zero-day research, this book from No Starch Press by Eugene “Spaceraccoon” Lim might be a good read. The table of contents looks pretty stacked, including:
Chapter 0: Day Zero
Chapter 1: Taint Analysis
Chapter 2: Mapping Code to Attack Surface
Chapter 3: Automated Variant Analysis
Chapter 4: Binary Taxonomy
Chapter 5: Source and Sink Discovery
Chapter 6: Hybrid Binary Analysis
Chapter 7: Quick and Dirty Fuzzing
Chapter 8: Coverage-Guided Fuzzing
Chapter 9: Fuzzing Everything
Chapter 10: Beyond Day Zero
Check it out below:
- Prompt Injecting
Using prompt injection as the source of a finding to traditional attack vectors can be an incredibly powerful primitive. I myself have found a few bugs by using specific prompts to reach different flows in the chatbot, which has unlocked a huge amount of attack surface.
Some cool research by Embracethered covers some pretty sweet things with prompt injection. The research shows you can sometimes poke holes right through those guardrails implemented in most GPTs now, and not just the usual user-level ones - these exploits can mess with ChatGPT’s operator-level instructions, the ones that are supposed to be untouchable.
Check out the full research here:
Also - all the other research on that blog is top-notch. If you want to dive into prompt injection in general, it’s probably worth a read.
Another cool thing the guys dropped on the pod is focusing on the conversion of formats, or asking a bot to convert input from one format to another. Asking it to convert markdown to HTML for example can provide a nice means of XSS.
Also, Nuclei dropped an AI flag, so if you’re looking for some neat ways to automate some of your bug bounty checks, check it out below:
And that’s it for this week. As always, keep hacking!