Skip to content

fix(security): block OAuth account takeover via pending exchange - #5345

Merged
Wei-Shaw merged 1 commit into
Wei-Shaw:mainfrom
puppywang:fix/oauth-pending-account-takeover
Aug 7, 2026
Merged

fix(security): block OAuth account takeover via pending exchange#5345
Wei-Shaw merged 1 commit into
Wei-Shaw:mainfrom
puppywang:fix/oauth-pending-account-takeover

Conversation

@puppywang

Copy link
Copy Markdown
Contributor

Summary

Fixes a critical account-takeover vulnerability (0day) in the pending OAuth flow: an attacker who only knows a victim's email address can bind their own LinuxDo / OIDC / WeChat / DingTalk identity to the victim's account and log in as them.

Attack chain

  1. Attacker logs in with their own OAuth account (intent=login) and lands on the email-completion step.
  2. Attacker submits the victim's email via POST /auth/oauth/pending/create-account with an arbitrary password. Because the email already exists, the handler transitions the pending session into the choice state with TargetUserID = victim, existing_account_bindable = trueno password, no email verification code, no captcha, no proof of ownership is ever checked on this path (the captcha check is skipped when the email exists).
  3. Attacker calls POST /auth/oauth/pending/exchange with an adoption decision (adopt_display_name / adopt_avatar — pure booleans unrelated to account ownership). ExchangePendingOAuthCompletion only guarded against email_completion / bind_login_required steps, so the request falls through to applyPendingOAuthAdoptionapplyPendingOAuthBinding, and shouldBindPendingOAuthIdentity returns true unconditionally for intent=login.
  4. The attacker's OAuth identity is now bound to the victim's account. On the next OAuth login, findOAuthIdentityUser resolves the attacker to the victim and the exchange endpoint issues the victim's token pair.

Fix

ExchangePendingOAuthCompletion now only performs adoption/binding for:

  1. Terminal login sessions (canIssueTokenPair == true) — the identity was already safely bound to that user during a completed login;
  2. bind_current_user sessions — initiated by an authenticated user whose binding target comes from the login-session cookie.

Every other state (including choose_account_action_required) returns the payload without binding the identity and without consuming the session, so the attacker cannot proceed to the next step either.

Testing

  • New regression test TestExchangePendingOAuthCompletionChoiceStateDoesNotBindIdentity reproduces the exact attack (choice-state session + adoption decision → identity not bound, session not consumed, no token issued).
  • All existing TestExchangePendingOAuthCompletion*, TestCreateOIDCOAuthAccount*, TestSendPendingOAuthVerifyCode*, and the full internal/handler package tests pass.

Recommendation for follow-up (not included)

Defense in depth: when create-account / send-verify-code discovers an existing email, the session should transition to a password/2FA-required state (bind_login_required semantics) instead of a choice state that implies bindability. Please also audit existing auth_identities rows for bindings whose upstream email does not match the owning user.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution! Before we can merge this PR, we need you to sign our Contributor License Agreement (CLA).

To sign, please reply with the following comment:

I have read the CLA Document and I hereby sign the CLA

You only need to sign once — it will be valid for all your future contributions to this project.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@puppywang

puppywang commented Aug 6, 2026 via email

Copy link
Copy Markdown
Contributor Author

@puppywang

Copy link
Copy Markdown
Contributor Author

recheck

@Wei-Shaw
Wei-Shaw merged commit 8991574 into Wei-Shaw:main Aug 7, 2026
7 of 8 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants