What Open Redirects Are and Why They Keep Appearing
An open redirect exists whenever a web application accepts a user-controlled URL parameter and issues an HTTP redirect to that value without validation. The classic shape is straightforward:
https://example.com/login?return_to=https://evil.example
After a successful login, the application redirects the browser to whatever return_to contains. If there is no validation, the attacker controls the destination. The user sees a legitimate domain in the initial link, trusts it, clicks through, and lands on a phishing page or malware dropper.
OWASP classifies open redirects under Unvalidated Redirects and Forwards. They appear in the OWASP Top 10 periodically and remain a fixture in bug-bounty programmes because developers consistently underestimate the trust chain they break.
The vulnerability is not theoretical. Open redirects are a practical building block in phishing campaigns, OAuth token theft, and session hijacking. They turn your domain into a trusted launchpad for attacks against your own users.
Why OAuth and SSO Flows Are Especially Vulnerable
OAuth 2.0 and OpenID Connect rely heavily on redirect URIs. The authorization server sends the user back to the client application at a registered redirect_uri after authentication. If that redirect URI validation is loose, an attacker can:
- Steal authorization codes by registering a redirect URI that points to their server
- Harvest tokens from the fragment portion of implicit-flow responses
- Chain redirects through an open redirect on the legitimate client to exfiltrate tokens to an external domain
SSO flows compound the risk. A single identity provider serves multiple relying parties. One open redirect on any relying party can be leveraged to phish users across the entire SSO ecosystem, because the initial URL genuinely originates from the identity provider's domain.
The redirect_uri Problem in Practice
The OAuth 2.0 specification (RFC 6749, Section 3.1.2.2) requires exact matching of redirect URIs. In practice, many implementations allow:
- Subdirectory matching (
https://app.example/callbackmatcheshttps://app.example/callback/anything) - Wildcard subdomains (
*.example.com) - Localhost with any port for development (which then leaks into production)
Each of these creates an attack surface. Exact-match validation is the only safe default.
OWASP Prevention Patterns
Pattern 1: Allowlist of Permitted Destinations
The most reliable approach is maintaining an explicit allowlist of permitted redirect targets. Every redirect parameter is checked against this list before the redirect is issued.
ALLOWED_REDIRECTS = {
"/dashboard",
"/account/settings",
"/projects",
}
def safe_redirect(requested_url):
if requested_url in ALLOWED_REDIRECTS:
return redirect(requested_url)
return redirect("/dashboard") # Safe default
When to use this: Applications with a finite, known set of post-login destinations. Most internal tools and admin panels fit this pattern.
Limitation: Does not scale when redirect targets are dynamic or user-generated.
Pattern 2: URL Validation with Origin Checks
When allowlisting individual paths is impractical, validate the URL structurally:
from urllib.parse import urlparse
ALLOWED_HOSTS = {"app.example.com", "docs.example.com"}
def validate_redirect(url):
parsed = urlparse(url)
# Reject any URL with a scheme other than HTTPS
if parsed.scheme and parsed.scheme != "https":
return False
# If a host is present, it must be in the allowlist
if parsed.netloc and parsed.netloc not in ALLOWED_HOSTS:
return False
# Reject URLs that contain authentication credentials
if parsed.username or parsed.password:
return False
return True
Pattern 3: Relative-Path Enforcement
Force all redirect targets to be relative paths. This eliminates the possibility of redirecting to an external domain entirely.
def is_safe_relative_path(url):
# Must not start with // (protocol-relative URL)
if url.startswith("//"):
return False
# Must not contain a scheme
if "://" in url:
return False
# Must start with /
if not url.startswith("/"):
return False
# Must not contain backslashes (IE/Edge normalisation)
if "\\" in url:
return False
return True
This is the recommended default for most web applications. If you can restrict redirects to relative paths on your own origin, do so.
Pattern 4: Signed Redirect Tokens
For flows where the redirect URL must be dynamic and external (such as OAuth), use signed tokens rather than raw URLs:
- Generate a redirect token containing the destination URL and a timestamp
- Sign it with an HMAC key known only to your server
- Pass the token as the redirect parameter instead of the raw URL
- On redirect, verify the signature and check the timestamp for freshness
This prevents tampering entirely. The attacker cannot forge a valid token without the signing key.
Common Bypass Techniques to Defend Against
Attackers have developed a substantial catalogue of bypass techniques. Your validation must account for all of them.
URL Parser Confusion
Different URL parsers interpret ambiguous URLs differently. An attacker exploits the gap between how your validation code parses the URL and how the browser interprets it.
https://[email protected] # Userinfo section
https://example.com%40evil.com # Encoded @
https://evil.com\.example.com # Backslash normalisation
https://example.com#.evil.com # Fragment confusion
Defence: Parse the URL with the same library the browser will use. Reject URLs containing @, \, or encoded variants of these characters in the host portion.
Protocol-Relative URLs
//evil.com/phish
The browser interprets this as https://evil.com/phish (or http://, matching the current page's protocol). Many validation routines only check for http:// or https:// prefixes and miss this.
Defence: Explicitly reject URLs starting with //.
Data and JavaScript URIs
data:text/html,<script>alert(1)</script>
javascript:alert(1)
These do not involve a network request but execute in the context of the redirecting origin.
Defence: Allowlist schemes explicitly. Only permit https (and http if genuinely needed). Reject everything else.
Double Encoding
https://example.com/redirect?url=https%253A%252F%252Fevil.com
If your application decodes the URL parameter twice, the first decode produces https%3A%2F%2Fevil.com, and the second produces https://evil.com.
Defence: Decode exactly once. Validate after decoding. If the decoded value still contains percent-encoded characters, reject it.
Open Redirect Chaining
An attacker finds an open redirect on trusted-partner.com and chains it through your OAuth flow:
https://your-idp.com/authorize?redirect_uri=https://your-app.com/callback?next=https://trusted-partner.com/redirect?url=https://evil.com
Defence: Validate the final destination, not just the immediate redirect target. Where possible, avoid passing through user-controlled redirect parameters at all.
Implementation Checklist
Use this checklist when auditing redirect handling in your applications:
- [ ] Inventory all redirect endpoints — search codebase for
redirect(,Location:,301,302,307,308,window.location,meta http-equiv="refresh" - [ ] Classify each redirect — static destination, relative path, or dynamic/user-controlled
- [ ] Apply relative-path enforcement to every redirect that does not require an absolute URL
- [ ] Implement an allowlist for redirects that must use absolute URLs
- [ ] Enforce exact-match
redirect_urivalidation in all OAuth/OIDC implementations - [ ] Reject URLs containing
@,\\, and non-HTTPS schemes in redirect parameters - [ ] Decode URL parameters exactly once before validation
- [ ] Log rejected redirect attempts for monitoring and incident response
- [ ] Add CSP
navigate-todirective (where browser support permits) as a defence-in-depth measure - [ ] Review third-party libraries for redirect behaviour — middleware, authentication libraries, and CMS plugins often introduce redirect endpoints you did not write
Verification Steps
Manual Testing
-
Basic open redirect test: Append
?return_to=https://evil.exampleto every redirect endpoint. Confirm the application does not follow the redirect. -
Protocol-relative test: Try
?return_to=//evil.example. Confirm rejection. -
Encoded bypass test: Try
?return_to=https%3A%2F%2Fevil.exampleand?return_to=https%253A%252F%252Fevil.example. Confirm both are rejected or redirect to a safe default. -
Userinfo bypass test: Try
?return_to=https://[email protected]. Confirm rejection. -
JavaScript URI test: Try
?return_to=javascript:alert(document.domain). Confirm rejection.
Automated Scanning
Use tools that specifically test for open redirects:
# Nuclei template for open redirect detection
nuclei -t http/vulnerabilities/open-redirect/ -u https://your-app.com
# Burp Suite active scan with redirect checks enabled
# Configure scope to include all redirect parameters
OAuth-Specific Verification
- Register a client with redirect URI
https://app.example.com/callback - Attempt authorisation with
redirect_uri=https://app.example.com/callback/../evil - Attempt authorisation with
redirect_uri=https://evil.example.com - Attempt authorisation with
redirect_uri=https://app.example.com/callback?next=https://evil.example.com - Confirm all four attempts are rejected by the authorization server
Gotchas
Framework defaults are not always safe. Django's LoginView validates next parameters against ALLOWED_HOSTS, but only if ALLOWED_HOSTS is properly configured. A wildcard * in ALLOWED_HOSTS disables redirect validation entirely.
JavaScript single-page applications often handle redirects client-side with window.location.href = params.get('redirect'). This is just as dangerous as a server-side redirect. Apply the same validation.
CDN and edge-function redirects (Cloudflare Workers, AWS Lambda@Edge) may implement redirect logic outside your main application. Audit these separately — they are easy to overlook.
Mobile deep links (myapp://) in redirect parameters can trigger unintended application launches. If your application only serves web content, restrict redirect schemes to https exclusively.
Meta refresh tags and JavaScript redirects bypass server-side redirect validation entirely if injected through other vulnerabilities (XSS). Open redirect prevention is one layer; it does not substitute for output encoding and XSS prevention.
Monitoring and Response
Log every redirect that is rejected by your validation logic. Include:
- Timestamp
- Source IP
- The rejected URL value
- The endpoint that received the request
- The authenticated user (if any)
A sudden spike in rejected redirects indicates active probing. Correlate with your security monitoring and operations alerting to catch campaigns early.
Set up alerts for patterns such as:
- More than 10 rejected redirects from a single IP within 5 minutes
- Rejected redirects containing domains associated with known phishing infrastructure
- Rejected redirects on OAuth endpoints specifically
Further Reading
- OWASP Unvalidated Redirects and Forwards Cheat Sheet
- RFC 6749 Section 3.1.2 — OAuth 2.0 Redirection Endpoint
- OAuth 2.0 Security Best Current Practice (RFC 9700)
- AI Crawler Control Without Losing Discoverability — related access-control considerations
- Security hub — all security articles
- Operations hub — monitoring and response resources