Lab 2 Forced OAuth profile linking
This lab gives you the option to attach a social media profile to your account so that you can log in via OAuth instead of using the normal username and password. Due to the insecure implementation of the OAuth flow by the client application, an attacker can manipulate this functionality to obtain access to other users' accounts.
To solve the lab, use a CSRF attack to attach your own social media profile to the admin user's account on the blog website, then access the admin panel and delete carlos.
The admin user will open anything you send from the exploit server and they always have an active session on the blog website.
You can log in to your own accounts using the following credentials:
Blog website account:
wiener:peterSocial media profile:
peter.wiener:hotdog
Feature Being Attacked
The blog app lets a logged-in user "attach" a social media profile to their existing account, so they can log in via OAuth instead of a password. This uses a normal OAuth flow - /auth → login/consent → redirect with code → /oauth-linking?code=... on the blog's backend, which links that social profile to whichever account is currently logged in.
Key Problem
The linking request has no state parameter (or any other CSRF protection):
GET /auth?client_id=...&redirect_uri=.../oauth-linking&response_type=code&...Because /oauth-linking?code=... is just a plain GET request with no CSRF token tying it to the user who originally started the flow, it becomes a CSRF-able endpoint. The code itself is normally single-use and tied to the attacker's own social account - but the server has no way of confirming who the browser making the /oauth-linking request actually is, beyond whatever session cookie is attached at request time.
The Exploit Logic
Attacker starts the "attach social profile" flow using their own social media account
Attacker intercepts (but doesn't let complete) the callback:
GET /oauth-linking?code=ATTACKER_CODEAttacker hosts this URL in a hidden
<iframe>on the exploit serverVictim (already logged into the blog as admin) visits the page → browser loads the iframe → sends
GET /oauth-linking?code=ATTACKER_CODEusing the victim's own session cookieServer links the attacker's social profile to the victim's (admin's) account - because it just uses whatever session cookie rides along, with zero verification that the person completing the OAuth flow is the same person who started it
Root Cause
Classic CSRF, just wrapped around an OAuth linking flow.
stateexists specifically to bind the authorization request and its callback to the same user session - without it, the callback can be triggered by (or on behalf of) any victim via a forged request, since the server can't distinguish "I initiated this" from "someone else initiated this and I just clicked a link/loaded an iframe."
The Fix
Generate a random, unguessable
statevalue when the "attach social profile" flow startsStore it tied to the current user's session
Require the
/oauth-linkingcallback to include the samestatevalue and validate it matches before performing the linkThis ensures the account being linked belongs to the session that initiated the request - not whichever session happens to be active when the callback fires
One-Line Takeaway
Any OAuth redirect/callback endpoint that changes account state (login, linking, permission grants) must use
stateto bind the request to the originating session - otherwise it's just a CSRF attack wearing OAuth clothing.
Last updated