For the complete documentation index, see llms.txt. This page is also available as Markdown.

Lab 1 Authentication bypass via OAuth implicit flow

This lab uses an OAuth service to allow users to log in with their social media account. Flawed validation by the client application makes it possible for an attacker to log in to other users' accounts without knowing their password.

To solve the lab, log in to Carlos's account. His email address is carlos@carlos-montoya.net.

You can log in with your own social media account using the following credentials: wiener:peter.


Step 1 - Client initiates the OAuth flow

What's happening: The blog app (the Client) redirects your browser to the OAuth server (the Authorization Server), asking it to authenticate you and hand back an access token directly (response_type=token = implicit flow, no code exchange step).

  • client_id - identifies the blog app to the OAuth server

  • redirect_uri - where to send you back afterward

  • scope=openid profile email - the blog is asking for your identity info

  • nonce - meant to bind the token to this specific request (replay protection)

Response: Redirect to /interaction/YY3GyzvSTLSepasg0vSyi - the OAuth server's own login/consent flow, tracked by that interaction ID.


Step 2 - User logs into the OAuth provider

What's happening: Since you're not logged into the OAuth provider yet, it shows a login form. You submit credentials (wiener:peter) here - this is you authenticating to the OAuth server itself, not the blog.

Response: 302 redirect back into the interaction flow, confirming login succeeded.


Step 3 - Consent confirmation

What's happening: This is the "Allow this app to access your profile/email?" consent step. Empty body just means "yes, confirmed" (the interaction ID already carries the context).

Response: 302 redirect to /auth/YY3GyzvSTLSepasg0vSyi - now the OAuth server proceeds to actually issue the token.


Step 4 - Token issuance

Response:

What's happening: The OAuth server has authenticated you (wiener) and generated a valid access token proving that fact. Because this is the implicit flow, the token is sent straight back in the URL fragment (#...), not a query string - fragments aren't sent to servers by browsers, so this is a deliberate design to keep the token from leaking to the redirect_uri's server logs directly. It lands in your browser only.

Key point: This token is legitimate and does correctly represent "wiener successfully logged in." Nothing wrong yet.


Step 5 - Client-side JS reads the fragment and calls the backend

What's happening: JavaScript on the /oauth-callback page extracts the access_token from the URL fragment. It also independently knows/fetches your email and username (likely from calling /userinfo client-side, or it was embedded somewhere), and packages all three into a POST to the blog's own backend to actually establish your logged-in session.

This is where the flaw lives. The backend receives three pieces of data:

  • token - proof an OAuth login happened

  • email, username - claims about who that login was for

The backend has two choices here: verify the token server-side to independently learn who it belongs to, or trust the email/username fields as given. This lab's backend does the latter.


Step 6 - The exploit

You intercept this last request and change it to:

What's happening: You still hold a 100% valid, real token - but it was issued for wiener. You're now claiming it belongs to carlos instead. Since the backend never calls back to the OAuth server to check "who does this token actually belong to?", it has no way to catch the mismatch. It just trusts the JSON and issues you a session as carlos.

Response: A valid session token - you're now logged in as carlos, having never touched their password or a token issued for them.


Why This Is Possible - Root Cause

The access token and the identity claim are logically separate in this exchange, but the backend treats them as if the client vouching for both together is sufficient. Correct design requires the server itself to resolve identity from the token - not accept identity as a sibling parameter.

The Fix

/authenticate should accept only the token:

json

Then server-side:

β†’ get the real email/username back from the OAuth server directly, and use that to create the session. There's then nothing left for an attacker to tamper with - the identity isn't attacker-supplied data at any point.

Last updated