Wildcard Browser Message Leaks Login Code, Enables Permanent Account Takeover
The Risk
One click on any web page the victim visited handed the attacker permanent control of the victim's business account on the review platform. The popup the victim signed into was the real login page with valid security indicators and unmodified branding, so there was no warning sign. After takeover, the attacker could silently change the account email with no second factor and no confirmation click, locking the victim out so completely that recovery required manual support intervention. Every business customer was affected.
The Vulnerability
The production login bundle for the business application contained a helper called handleOpener. It was executed on the real login origin whenever the URL carried a ?flow=popup parameter. Its job was to relay the login result back to the page that opened the popup, but it used a wildcard message target:
export const handleOpener = (authenticationResponse) => {
const query = qs.parse(location.search.substr(1));
const popupFlow = query.flow === 'popup';
if (popupFlow && opener) {
const loginSuccessMessage = {
name: 'login-success',
token: authenticationResponse.token,
code: authenticationResponse.code,
};
opener.postMessage(loginSuccessMessage, '*'); // wildcard target
}
if (popupFlow) { window.close(); return true; }
return false;
}; The wildcard '*' target told the browser to deliver the message containing the OAuth code (and the identity token on the federated login branches) to any window that opened the popup, regardless of its origin. An attacker-controlled page that opened the real login popup received the login code directly.
Two additional weaknesses turned the code leak into permanent takeover:
- The captured login code exchanged at the token endpoint for a bearer with a 100-hour lifetime.
- That bearer accepted a silent
PATCHagainst the victim's business-user record to mass-assign a new email address with no re-authentication, no second factor, no current-password challenge, and no confirmation click on the new address. The platform's public availability oracle then confirmed the rebind landed.
The Attack
- The attacker hosts a branded lure page on any HTTPS site. The page contains a "Continue with Business Account" button.
- The victim opens the lure. Clicking the button opens a popup directly to the real login origin with valid certificates and unmodified branding. The popup URL carries
?flow=popup. - The victim signs into the popup with their normal flow (email magic link, federated identity provider, or single sign-on). The login completes inside the genuine origin.
- The login helper runs and posts the success message containing the login code and bearer to any opener window via the wildcard target. The lure page receives it on its message listener. The popup self-closes.
- The attacker's server exchanges the captured login code at the token endpoint and receives a bearer authorizing the victim's account for 100 hours.
- The attacker sends a single
PATCHrequest to the business-user record with{"email":"[email protected]"}. The server returns204. The email column is rebound. - Subsequent magic-link sign-ins for that business account now deliver to the attacker. The original bearer returns
403after the rebind, but its job is done. The victim's "log out everywhere" cannot evict the attacker, because previously issued bearers continue to authenticate the API for their full lifetime.
The popup flow also worked for federated identity provider branches and single sign-on. No XSS, no cookie quirk, and no privileged network position was required.
The Impact
- One-click silent takeover of any business customer of the platform. The victim sees only the real login surface and a "you're signed in" experience.
- Permanent account loss. After the silent email rebind, the victim cannot self-recover. Recovery requires support to manually revert the email column.
- Capability after takeover includes publishing fraudulent business responses to consumer reviews, abusing the verified-business trust signal, running invitation campaigns against the victim's customer list, viewing analytics, and modifying billing details.
- Regulatory exposure. Brand impersonation and review fraud against consumers map directly to consumer-protection and data-protection regulatory regimes.
- Token revocation gap. The token-revoke endpoint only cleared the refresh cookie. Previously issued access bearers continued to authenticate for their full 100-hour lifetime, so manual revocation did not evict an active attacker.
Remediation
- Replace the wildcard target with a single-origin allowlist:
opener.postMessage(loginSuccessMessage, Config.DashboardUrl). Reject any other origin outright. - If the popup flow has no first-party consumer, drop the
?flow=popupbranch entirely. The wildcard was only reachable because the popup branch sent the code back through the browser message channel. - Bind
redirect_uritoclient_idat the token endpoint so a captured code cannot be exchanged from an attacker host even if a redirect-URI bypass appears in the future. - Require strong re-authentication (current password or a fresh second factor) before any email-change
PATCHon the business-user record. Send a confirmation email to the new address that must be clicked before the change takes effect. - Fix the token-revoke endpoint to invalidate previously issued access bearers, not only the refresh cookie. Short-lived access tokens with online revocation checks would close the 100-hour eviction gap.
- Audit every other
postMessagecall site in the production bundles for wildcard targets and apply the same allowlist treatment.