- Critical Thinking - Bug Bounty Podcast
- Posts
- [HackerNotes Ep. 151]: Client-Side Advanced Topics with Rhynorater
[HackerNotes Ep. 151]: Client-Side Advanced Topics with Rhynorater
In this episode of CTBB podcast, Justin and Joseph dive deep into advanced client-side exploitation techniques, covering third-party cookie partitioning, iframe tricks, URL parsing nuances, and more.
Hacker TL;DR
Null Origin Bypasses: Sandbox iframes with
nullorigins bypassevent.originchecks againstwindow.originbecause string comparison of"null"passes validation.CHIPS Partitioning: Cookies with the
partitionedattribute are keyed by scheme + eTLD+1 + iframed host, not just domain.Sandbox Inheritance:
window.open()from sandboxed iframes inherits sandbox properties (exceptallow-top-level-navigation).Client-Side Routes: Single-page applications expose their entire routing logic in JavaScript - reverse engineer route definitions to discover parameters and endpoints.
With ThreatLocker® Elevation Control, you eliminate the security risks of local admin rights while keeping your team productive. While traditional endpoint privilege management tools force users through complex authentication workflows, our application-centric Zero Trust EPM solution provides your organization with:
✓ Granular elevation policies for specific applications, not users
✓ Seamless privileged access without credential prompts
✓ Time-based elevation for installs that automatically expires
✓ Integration with Ringfencing™ to prevent network movement
✓ Mobile approvals for just-in-time elevation requests
Start securing privileged access—without slowing down your team.
In the News
Quirky XSS payloads posted by @nowaskyjr: https://x.com/nowaskyjr/status/1993421017381744974 & https://x.com/nowaskyjr/status/1992717862398800081
A lightweight HTTP repeater inside DevTools: https://x.com/BourAbdelhadi/status/1992622964077179229
Cookies Having Independent Partitioned State (CHIPS) changes how browsers handle cookies in embedded contexts.
When the partitioned attribute is attached to a cookie in Chrome, cookies are no longer scoped just to the domain. They now have a key tied to the top-level page: {(scheme + eTLD+1}, (iframed host)}
postMessage: Beyond the Basics
Analogy: Think of postMessage as a letter sent directly between windows via JavaScript. The main examples of a "window" are an open tab, separate browser window, or a window inside an iframe.
The receiving window needs to register a listener to process the data sent.
Event Object Properties
When you register a postMessage listener, an event object is passed to the listener:
event.data- Contains the message data.event.source- Reference to the window that sent the postMessage.event.origin- The origin of the window that sent the postMessage.
The Null Origin Bypass
You'll commonly see event.origin checked against window.origin to ensure the origin sending the message is the same one receiving it:
window.addEventListener('message', function(event) {
if (event.origin !== window.origin) {
return;
}
// Process message
});
The Attack:
If you create a sandbox iframe, the origin becomes null. If you allow it to control popups and open a new window, you can specify any site but it will inherit the null origin. This bypasses event.origin checks against window.origin because a string comparison is made: "null" === "null".
View this in action: https://poc.rhynorater.com/research/nullDemo.html
Important Detail: If you don't specify allow-same-origin in the sandbox attributes, a sandboxed resource is treated as being from an opaque origin, ensuring it will always fail Same Origin Policy checks. If you have two iframes that are both null origin, they'll fail the same-origin check internally, but their string representation is the same ("null"), allowing the bypass.
Non-String Messages
The first parameter in .postMessage() doesn't have to be a string - it can also be an object. You can send complex types like BigInt. You're not constrained to the basic JSON types like string, array, and integer.
Message Ports: Unheard Whispers
Message ports are a browser feature - think of them like a can with string to another can. Instead of postMessaging into a window, you send to a port and only the person with a reference can listen.
Tools may not hook into message ports, making them unheard whispers during testing. They still have an event object. Even FancyTracker can have issues, so set conditional breakpoints on event listeners and use console.log() as a redundant hook to ensure all communication is being monitored.
iframe Tricks: The Attacker's Swiss Army Knife
Sandbox Attributes
Inside the sandbox attribute, you can specify certain permissions and understanding these permissions is essential to craft effective payloads. https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe#attributes
The Basic Attack Pattern
Abstract: An iframe provides a window reference. Due to this a victim can visit your website, and if you load an iframe with a vulnerable site, you can reference that window and attack the victim on your page.
"I need a place to stand." - Archijustin
Sandbox Inheritance via window.open()
Again, you can create an attacker page with a null origin attacker-controlled iframe. Set the iframe settings to allow-popups and allow-scripts. From this iframe, use window.open() to the victim site - it will have a null origin and inherit the sandbox properties.
Example Use Case: Shout out to @Jorian, who used allow-modals, allow-popups, allow-same-origin, and allow-scripts BUT NOT allow-forms. This prevented a page that was auto-submitting a form from doing so, giving him the control he needed for his attack.

Important Exception: The exception to sandbox inheritance is allow-top-level-navigation (which allows navigating the URL bar for that top tab). Windows that have inherited sandbox properties from an iframe but were opened via window.open() in a new tab won't get this permission - "We can't let them navigate themselves."
The credentialless Attribute
The credentialless attribute on iframes means it uses a new ephemeral context - no cookies, no local storage, nothing associated with that domain.
Use Case: This is useful if you need a cookie to not be set for an attack to happen, but normal visits will set it. You can get users in a state where the cookie is not set but they are still logged in.
Important: The credentialless attribute does NOT get inherited.
Window Name Hijacking
The name attribute controls the name of the frame. You can name your frames and windows, and there are scenarios where you can force a victim page to open a window.open() into that iframe.
Attack Flow:
You have an attacker-controlled page.
You embed inside that page an iframe to the victim site called
"abc".You do
window.open()to a victim page.That victim page goes through a OAuth flow and does a
window.open()to"abc".It will look for any other pages in the browsing context that exist before opening a new one (must be same origin as itself).
Since you embedded that iframe in the first place, you control where it's pointing to - you can redirect elsewhere or put something in the hash.
When it redirects back to the victim page and that addition is still in the hash, it may read it.
View this in action: https://poc.rhynorater.com/iframeHijacking/attackerPage.html
AI Markdown Link Injection
Cool Side-Note: If AI generates Markdown output with a link in it, and the <a> tag allows the target attribute (for example, Gemini has an invisible iframe), you can set target= to the window name. When the link is clicked, it will open into the invisible iframe.
Now you have a persistent invisible iframe with your attacker-controlled page inside of Gemini. Unless the iframe is sandboxed, you can do things like top.location.navigate() to change the top-level page, send postMessages from a privileged position, or bypass COOP.
URL Parsing: Edge Cases and Exploits
Backslash to Forward Slash Conversion
Often times \/ gets converted into //, which allows you to produce an absolute URL instead of a relative one.
JavaScript URI Hostname Parsing
Recently, Chrome conformed to Safari behavior about parsing JavaScript URIs such that they can have a hostname: javascript://hostname.com/
In this case, hostname.com is parsed as the hostname, which is weird because the javascript: scheme shouldn't have a hostname.
Exploit: If there's a new URL() parsing of this scheme and it does .hostname and compares it to the hostname of this page, then when they do window.location = this, it will run JavaScript. You can use %0a (javascript://%0a) to use // as a comment and put yourself on a new line.
The Null String Origin Quirk
The string "null" that represents the null origin is weird because it's a string but is perceived in different ways.
Before the URL parsing framework in JavaScript, a common way to parse a URL was to create an <a> tag and set the href:
// Old technique for parsing URLs
var a = document.createElement('a');
a.href = event.origin;
var hostname = a.hostname;
The Vulnerability: If you want to extract the hostname from a postMessage in a reliable way using this technique, and you send this from a null origin, it passes "null" into the href of the tag. It's treated as a relative URL /null, and the hostname of a relative URL is the page we are currently on.
Note: This doesn't work with new URL().
URLSearchParams
URLSearchParams parses query parameters - a good place for DOM Logger++ hooks and CTRL+F to find it in code during testing.
onHashChange Events
The onhashchange event is something you can control on your attacker page:
window.open()to victim page.window.open()again to replace the hash and trigger theonhashchangeevent.
CSPT: Client-Side Path Traversal
A CSPT subset, similar to "request hijacking" involves taking a query parameter or path and injecting it directly into the domain, effectively overriding the host.
Browser vs. Fetch Perception
You can exploit the difference between what the browser perceives and what hits a fetch sink. Whitespace and newlines get stripped from anything passed to fetch() as the hostname.
Example: rhynorater%09.com → The %09 gets stripped out by fetch().
Takeaway: Be on the lookout for encoding techniques that create parsing differentials.
In single-page applications, client-side routing is overpowered - you have access to route definitions since the JavaScript is right in front of you.
Testing Strategy:
Reverse engineer to find where the routes are defined.
Check out every route.
Look for syntax like
:path-id, which means they're taking path parameters.The framework will likely be consistent in how parameters are obtained.
Understand every parameter you can supply.
CSRF via Callback Routes
With SameSite cookies, CSRFs are challenging. One way you can exploit is to make sure the request originates via the victim page.
Sometimes this happens via callback routes where it hits a 3rd-party provider and comes back to a specific route that is responsible for completing the process.
If you can grab attacker-controlled data and redirect the victim to the finalized route, it will send a POST request - giving you a CSRF-like attack.
Check out Doyenseс's Exploiting Client-Side Path Traversal to Perform Cross-Site Request Forgery - Introducing CSPT2CSRF blog for more details.
Bonus: DOM Element Execution Timing
Terjanq's 2021 post highlights a weird behavior about elements created and inserted into the DOM:
Before an element is actually inserted, it will still run:
var div = document.createElement('div');
div.innerHTML = '<img src=x onerror=alert()>';
// Alert executes before the div is inserted into the DOM
This can be leveraged in certain XSS scenarios where you have control over element creation but not direct DOM insertion.
Closing Thoughts
Client-side exploitation continues to evolve with new browser features and security mechanisms. Understanding the nuances of:
Cookie partitioning and storage contexts
Origin validation and null origin behaviors
Sandbox iframe attributes and inheritance
URL parsing differentials
Client-side routing architectures
...provides you with a toolkit of exploit capabilities.
The key is understanding how browsers actually behave versus how developers think they behave. That gap is where vulnerabilities live.
And as always, keep hacking!
