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.