OAuth Account Linking Bugs That Lead to Account Takeover
OAuth login is usually tested as a fresh sign-in flow. Account linking is messier: an existing local account attaches Google, GitHub, or another identity and expects both login methods to reach the same profile. That merge operation can become account takeover if the application links on an unverified claim or lets an attacker steer the callback.
Two accounts and two identity-provider users are enough to test it cleanly.
Write down the identities
Create local accounts A and B, plus provider identities P1 and P2. Give each a visible canary profile value. Keep separate browser containers so cookies never blur the result.
Link P1 to A through the intended settings flow. Capture the authorization request and callback: state, redirect_uri, scopes, provider user ID, email, and any local-account reference. Then unlink it if the product supports that. You now know the normal transaction.
The key question is how the server decides both sides of the link:
- Which logged-in local account requested linking?
- Which provider identity returned?
- Is the provider email verified?
- Was the user asked to reauthenticate before changing login methods?
If either side comes from a browser-controlled parameter rather than trusted session state, push there.
Identity collision and email trust
Some applications see email=user@example.com from an OAuth profile and automatically merge it into the local account with that email. Test providers that allow unverified or mutable email claims. If P2 can claim B's address without provider verification and the target app links by email alone, P2 may log into B.
Do not target a stranger. Set B's address to one you control, reproduce the collision, then sign in with P2 and show B's canary.
Provider subject identifiers are safer than email, but only when paired with the issuer. A sub from one provider must not collide with the same string from another. Also check tenant boundaries in enterprise OAuth: the same email in an attacker-controlled tenant should not silently become the corporate identity.
Login CSRF in the linking flow
Missing or weak state can reverse the victim and attacker. Start linking P1 while logged into A, capture the callback before it is consumed, then model B visiting that callback while logged in. If P1 attaches to B, A can later sign in through P1 and land in B's account.
This is account-linking login CSRF. The callback must be bound to the browser session that initiated it, single-use, and short-lived. A static state value or a state token not tied to the local session is not enough.
Try linking without recent password or MFA confirmation. A CSRF or unattended session becomes much more damaging when adding a new login method requires no reauthentication or notification.
Prove takeover, then undo it
After the unexpected link, log out completely and sign in using the attacker's provider identity. Show the victim account's canary and one harmless authenticated action. Then unlink the identity and restore both test accounts.
Report the exact identity mapping error, not just "OAuth is broken." Include the authorization and callback sequence, which claims were trusted, and whether the victim receives an alert. Fixes include issuer-plus-subject identity keys, requiring verified emails, binding state to the initiating session and action, recent reauthentication, and clear notifications with a recovery path.