The OAuth Happy Path Was a Lie: One-Click Account Takeover via Facebook
The target in this case was a mainstream e-commerce platform with Facebook login enabled. While reviewing their OAuth integration, I noticed the flow relied on Facebook as the identity provider (IdP) — and that immediately triggered a reflex.
Whenever I see Facebook or Google OAuth on a target, I always test the Jayesh trick:
Remove the
This technique fails 99% of the time — as it should — because most properly built systems only trust verified emails returned directly by the OAuth provider. But here, it didn’t.
If you’re new to OAuth or want a quick visual refresher, here’s a super simple diagram of how the standard OAuth 2.0 flow works (Authorization Code grant): 👉 View interactive diagram
TL;DR
- Bug class: OAuth misconfiguration → full account takeover (ATO).
- Attack cost: < 2 minutes, one throwaway Facebook account.
- User interaction: Victim only clicks their own confirmation email – no phishing.
- Reach: Any account whose email is ever unverified. Exploit is repeatable even after verification or address change (persistence).
- Root cause: App relies on post‑OAuth self‑reported email, not provider‑verified email / UID binding.
Full Attack Narrative
Step | Attacker Action | Server Behaviour |
---|---|---|
1 | Click “Login with Facebook” | Redirects to FB OAuth |
2 | Uncheck email scope | FB returns with no email claim |
3 | App asks for email → type [email protected] | App stores email + temp token |
4 | App sends normal confirmation link to victim | Victim inbox |
5 | Victim clicks (looks 100% legit) | Email gets marked verified |
6 | OAuth flow completes → attacker receives session cookies | Dashboard shows orders, PII, etc. |
Choosing log-in with facebook:
We can see that we can change the scope selection:
Removing email:
Victims click verfication email been set to him, then ATO:
Raw Request/Response
After attacker supplies the victim’s email:
POST /oauth/facebook/email HTTP/2
Host: target.com
Content-Type: application/x-www-form-urlencoded
[email protected]
Server response:
HTTP/2 200 OK
Set-Cookie: session=…; Secure; HttpOnly
Location: /verify-email-sent
After the victim clicks the confirmation link:
GET /signin/term_accept/verify_email/abc123 HTTP/2
This results in:
→ 302 /dashboard (already logged in as victim)
Persistence Check
- Victim verifies email? Still owned.
- Victim changes email/password? Still owned. The binding is FB UID ↔ local account; email no longer matters.
Business Impact
- Full read/write access to personal data, order history, saved addresses, loyalty points.
- Possible stored‑card misuse if “one‑click reorder” exists.
- Highly automatable → credential‑less mass takeover campaign.
Root‑Cause
- Trusting user‑supplied email after OAuth fallback.
- No server‑side ownership check against existing unverified accounts.
- Email confirmation endpoint doesn’t verify that the session requesting the send owns the address.
- Persisted binding of FB UID to account without re‑verification on profile changes.
Takeaways
- Always untick mandatory scopes in social logins.
- Test both pre and post email verification states.
- Re‑run after profile edits – persistence is an extra bounty line.
Timeline
Date | Event |
---|---|
24 May 2025 | Bug discovered during routine OAuth fuzzing |
25 May 2025 | PoC recorded; report filed |
TBD | Vendor triage / fix |