Skip to content

[Security] SSO token + UID posted to window.opener without targetOrigin (session takeover) #522

Description

@Ra-inskyy

Summary

ExternalSSOConsumer in the account app forwards the SSO token + UID to window.opener via postMessage without a targetOrigin argument (defaults to "*") and without verifying the opener is a Proton origin. Any page that opens the SSO popup becomes the opener and receives the victim's session credentials.

Vulnerable code

applications/account/src/app/content/ExternalSSOConsumer.tsx:47-51 (current master):

if (uid && token && window.opener) {
    flow = ExternalSSOFlow.Sp;
    window.opener.postMessage({ action: 'sso', payload: { token, uid } });
    await wait(5000);
}

No second argument to postMessage → targetOrigin defaults to "*" → the message is delivered to any origin. The comment above the block states the intent ("opener means it was opened through another tab") but nothing verifies that the opener is actually a Proton tab.

Attack chain

  1. Victim (logged into Proton, active session) visits attacker's page.
  2. Attacker's page calls window.open('https://account.proton.me/sso/login?username=<victim>') — attacker's page becomes the opener.
  3. SSO flow completes with the victim's session; Proton redirects to /sso/login#token=<real>&uid=<real>.
  4. ExternalSSOConsumer parses token + uid from the hash and postMessagees them to the attacker's page.
  5. Attacker authenticates as the victim (session takeover).

Impact

Account/session takeover — attacker obtains a valid token/uid pair for the victim's account without the password.

Suggested fix

Always pass the expected origin (or the validated opener origin) as the second argument:

window.opener.postMessage({ action: 'sso', payload: { token, uid } }, 'https://account.proton.me');

Additionally, verify window.opener is a Proton origin before sending, or use a handshake (opener sends a challenge, popup replies).

Note

Reported publicly per the user's request; the code is already public in this repo. Happy to coordinate a private disclosure / remove this issue if Proton prefers handling it via security@proton.me first.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions