Full Report
Web pages are intentionally isolated from one another. It would be insecure if another website could read the contents of your page. Or, use cookies to retrieve sensitive information. So, browsers with features like the same-origin policy (SOP) and others prevent these types of attacks. Regardless, it is still important to allow for the same cross-website communication. The most common way is postMessage. A lesser-known mechanism is the MessageChannel API. This creates a private, bidirectional communication pipe between two contexts. Once a MessagePort is transferred, the channel is considered secure. This functionality was used on SSO login embedded within an iframe and used to deliver OAuth authorization codes ot the parent application. With postMessage, it's common to miss an origin check. With MessageChannel, it works differently. You create a channel with two ports, you send one of the ports to another page, and use the other port yourself. There's no need to verify the origin because it's inherently private. So, how does the other page get this port? postMessage of course! To flow for logging in worked as follows: A first-party application, such as an online store, embeds the SSO login page in an iframe. SSO iframe broadcasts readiness to its parent via postMessage(). The parent responds with a MessagePort. User authenticates in the iframe. On success, the SSO provider sends the auth code via the MessagePort. The parent echagnes the code for a JWT session via server-side calls. This flow was reasonably solid but had a few subtle issues with it. First, there was no framing protection on the SSO login pages. This makes it possible to attack the parent frame. Second, the postMessage with the port was using a * as the sender. Additionally, the origin wasn't being validated on the sender. In practice, this means that the first submitted port wins. The attack works by first hosting a page on any domain that has the SSO login page (parent) in an iframe. When the SSO parent broadcasts that it's ready, the attacker creates a message channel and sends the port to the iframe. This allows the attacker's website to own the communication between the SSO iframe and the standard website. This leads to intercepting the auth code, leading to an account takeover. The vulnerability is awesome! I also enjoyed the discussion of the threat model. However, I thought the framing of the bug was a little extreme and degrading. The constant use of the word failure, wrong, broke... if we want security to get a better rep, it needs to be more constructive imo. Also, if you have to click on a website link, then it's not zero-click.
Analysis Summary
# Vulnerability: MessagePort Injection in SSO iframe Architecture
## CVE Details
- **CVE ID**: Pending (Vendor name withheld until full patch deployment)
- **CVSS Score**: 9.3 (Critical)
- **CWE**: CWE-345 (Insufficient Verification of Data Authenticity), CWE-1021 (Improper Restriction of Render-it in an Iframe)
## Affected Systems
- **Products**: Major Platform SSO (Single Sign-On) provider.
- **Versions**: All versions prior to the 2024/2025 remediation deployment.
- **Configurations**: SSO implementations using an iframe-based login flow that utilizes `MessageChannel` for delivering OAuth codes back to a parent application.
## Vulnerability Description
The vulnerability arises from a flawed implementation of the `MessageChannel` API during the OAuth authorization flow. The architecture relies on three primary security failures:
1. **Missing Framing Protection**: The SSO login page lacks `X-Frame-Options` or `Content-Security-Policy: frame-ancestors` headers, allowing any origin to embed the login page in an iframe.
2. **Insecure `postMessage` Broadcast**: When the SSO iframe loads, it broadcasts a "readiness" signal using a wildcard (`*`) target origin, allowing an attacker-controlled parent page to receive the signal.
3. **Missing Origin Validation on Port Transfer**: Upon receiving the readiness signal, the parent sends a `MessagePort` to the iframe. The SSO iframe validates the *structure* of the message but fails to verify the `event.origin` of the sender.
Because `MessagePort` communication is considered "inherently private" once established, the browser does not provide origin information for subsequent messages. By injecting a malicious port before the legitimate application can, an attacker "owns" the communication channel.
## Exploitation
- **Status**: PoC available; reported via Bug Bounty.
- **Complexity**: Low (Standard web technologies; works in modern browsers).
- **Attack Vector**: Network (Attacker-hosted malicious website).
## Impact
- **Confidentiality**: High (Full access to OAuth authorization codes and subsequent PII).
- **Integrity**: High (Full account takeover; ability to modify account data or perform actions as the user).
- **Availability**: None.
## Remediation
### Patches
- The vendor is currently deploying fixes. Implementers should ensure SSO components are updated to versions that include origin enforcement.
### Workarounds
- **Strict Origin Checking**: Always verify `event.origin` during the `onmessage` handshaking phase before accepting a `MessagePort`.
- **Frame Protection**: Implement `Content-Security-Policy: frame-ancestors 'self' [trusted_domains]` to prevent unauthorized embedding.
- **Targeted postMessage**: Replace wildcard `*` with specific, trusted origins when broadcasting readiness signals.
## Detection
- **Indicators of Compromise**: Unexpected OAuth authorization codes being exchanged from IP addresses inconsistent with the user's initial login location.
- **Detection Methods**: Audit client-side JavaScript for `window.postMessage` calls that lack origin validation logic or use the `*` wildcard for sensitive data/port transfers.
## References
- labs.trace37[.]com/blog/pkce-bypass-oauth-account-takeover/
- labs.trace37[.]com/ (Original Research: "Hijacking the Channel")
- developer.mozilla[.]org/en-US/docs/Web/API/MessageChannel