Most OAuth 2.0 implementations don’t fail because the core cryptography is broken; they fail because developers treat the redirect_uri parameter like a friendly suggestion rather than a strict security boundary.
When an Authorization Server relies on prefix matching, domain wildcards, or regex path traversal to validate callback URLs, it creates an implicit trust relationship with every path, open redirector, and XSS vector on the target origin. If an attacker can force the authorization server to send a valid authorization code to a location under their control, the target account is effectively compromised.
The Flaw: Why “Flexible” OAuth Allowlists Are Broken
The OAuth 2.0 specification (RFC 6749) explicitly mandates that authorization servers must validate the incoming redirect_uri against pre-registered URIs. However, to accommodate dynamic environments—such as multi-tenant subdomains or localization paths—identity providers often implement loose pattern matching.
Common validation flaws include:
- Substring / Domain Confusion: Matching
https://app.example.comagainsthttps://app.example.com.attacker.comorhttps://attacker.com/app.example.com. - Path Traversal Permissiveness: Allowing
https://app.example.com/oauth/callback/../../attackerbecause the string starts with the registered prefixhttps://app.example.com/oauth/callback. - Wildcard Subdomains: Trusting
https://*.example.com/callback, where any compromised or user-generated subdomain (e.g., via S3 bucket takeover) can receive sensitive credentials.
When loose validation is active, the Authorization Server issues a 302 Found response containing the authorization code in the query string (?code=AUTH_CODE) to whatever destination satisfies the weak check.
Tracing the Leak: The Attack Flow
To understand the mechanics, consider a standard OAuth 2.0 Authorization Code Grant where the legitimate client application registers https://client.example.com/callback.
Victim Browser Authorization Server Attacker Infrastructure
| | |
| 1. Initiates Auth (Manipulated) | |
|--------------------------------->| |
| redirect_uri=.../callback/.. | |
| | |
| 2. Validates prefix & issues code| |
|<---------------------------------| |
| 302 Redirect to Attacker Origin | |
| | |
| 3. Browser follows redirect with ?code=... |
|-------------------------------------------------------------------->|
- Crafting the Vector: The attacker identifies an open redirect or a directory on
client.example.comwhere query parameters or Referer headers leak to an external domain (e.g.,https://client.example.com/outbound?url=https://attacker.com). - Inducing the Victim: The attacker tricks an authenticated user into clicking an authorization request link where the
redirect_uriis altered:https://auth.example.com/authorize?response_type=code&client_id=12345&redirect_uri=https://client.example.com/callback/../../outbound?url=https://attacker.com&state=xyz - Validation Failure: The Authorization Server checks if
https://client.example.com/callback/../../outboundstarts withhttps://client.example.com/callback. It passes the check due to loose prefix matching. - Code Exfiltration: The Authorization Server redirects the victim’s browser to the manipulated path:
302 Location: https://client.example.com/callback/../../outbound?url=https://attacker.com&code=STOLEN_AUTH_CODE&state=xyz - Account Takeover: The page at
/outboundforwards the request (or leaks theRefererheader) toattacker.com, delivering thecode. The attacker immediately exchangesSTOLEN_AUTH_CODEat the provider’s/tokenendpoint to acquire an access token for the victim’s account.
Reproducing the Issue locally
To test URI matching logic safely, set up a minimal mock authorization handler using standard Python tools to observe how loose string matching fails compared to strict parsing.
Create a test script test_match.py to compare matching logic:
from urllib.parse import urlparse
REGISTERED_URI = "https://app.example.com/oauth/callback"
def insecure_prefix_match(input_uri):
# DANGEROUS: Simple prefix check allows path traversal
return input_uri.startswith(REGISTERED_URI)
def secure_exact_match(input_uri):
# CORRECT: String-exact match requirement
return input_uri == REGISTERED_URI
# Test Payload with Path Traversal
payload_uri = "https://app.example.com/oauth/callback/../../external"
print(f"Insecure Match Result: {insecure_prefix_match(payload_uri)}") # Evaluates to True
print(f"Secure Match Result: {secure_exact_match(payload_uri)}") # Evaluates to False
Run the validation check:
python3 test_match.py
Observe that insecure_prefix_match returns True for the traversal attempt, demonstrating how vulnerable servers accept unauthorized destination paths.
Securing the Flow: Enforcing Exact Matching
Fixing this vulnerability requires removing all non-exact string comparison logic from the authorization server’s client validation engine.
1. Require Exact String Comparison
The Authorization Server must perform a strict, byte-for-byte string comparison between the incoming redirect_uri and the pre-registered list. Do not parse URLs prior to comparison or strip trailing slashes automatically.
In Node.js/Express authorization logic, enforce strict equality:
// Secure authorization request handler
app.get('/authorize', (req, res) => {
const { client_id, redirect_uri } = req.query;
const client = getRegisteredClient(client_id);
// Exact match validation against array of explicitly approved URIs
if (!client.allowedRedirectUris.includes(redirect_uri)) {
return res.status(400).send('Invalid redirect_uri parameter.');
}
// Proceed with authorization logic
});
2. Implement PKCE (Proof Key for Code Exchange)
While exact matching prevents the parameter leakage vector, implementing PKCE (RFC 7636) for all flows—including confidential clients—provides defense-in-depth. With PKCE, even if an authorization code is intercepted via a leaked URI, the attacker cannot exchange it at the /token endpoint without the dynamically generated code_verifier.
3. Remove Open Redirectors
Ensure that client applications host no open redirect endpoints on the same origin or path structure as the registered OAuth callbacks. Scan endpoints regularly to verify that query parameters like next=, redirect=, or url= do not issue unvalidated HTTP 302 responses.
Related content
Beyond URL Fetching: How IMDSv1 Turns SSRF into Full Cloud Compromise
ResearchBreaking Active Directory Attack Paths: From SharpHound Data to Choke-Point Remediation
ResearchBuilding a High-Signal External Recon Pipeline That Actually Scales
ResearchCatching Fodhelper UAC Bypasses: Mechanics and Telemetry Engineering
Want a second set of eyes on your security posture?
Let's talk about where your real exposure is.
Book an advisory call