Safeguard
Application Security•

CWE-601 Open Redirect: How It Happens and How to Fix It

An open redirect turns your trusted domain into a launchpad for phishing and OAuth theft. How the bug works, how to test for it, and how to fix it properly.

Safeguard Security Team
5 min read

CWE-601, URL Redirection to Untrusted Site, is better known as an open redirect. The web application accepts user-controlled input that names a link to an external site, and uses that link in a redirect.

It looks harmless, which is why it survives so long. A link that starts at your real domain and ends somewhere hostile borrows all the trust your domain has earned.

How it happens

Login pages, logout pages, and search scripts often take a parameter that says where to go next.

https://example.com/login?next=https://evil.example/

The server logs the user in and then sends a redirect to whatever next holds.

# Vulnerable pattern (Flask)
from flask import request, redirect

@app.route("/go")
def go():
    return redirect(request.args.get("next"))

Validation is where it usually goes wrong. MITRE's record includes CVE-2020-11053, a chain in a Go-based OAuth2 reverse proxy that could send the authenticated user to another site at the end of the authentication flow, where a redirect URL with HTML-encoded whitespace characters bypassed the validation. A check existed, and a crafted encoding still got past it.

Other entries show the older variants: CVE-2008-2951 and CVE-2008-2052 are open redirects used for phishing through a URL passed as a parameter, and CVE-2005-4206 loaded the URL into a frame so it appeared to be part of a valid page.

What an attacker gains

MITRE lists two main outcomes.

  • Phishing. By modifying the URL value to a malicious site, an attacker can redirect users to an untrusted page, and the link looks legitimate because it starts on your domain.
  • Malware and downloads. The user may land on a page that tries to compromise their system, and in some cases an open redirect can enable the immediate download of a file without the user's permission.

The OAuth case is worse. A redirect at the end of an authentication flow can carry a user, and sometimes their tokens, to a place the attacker controls.

How to find it

The record is realistic about tooling. This weakness does not typically appear many times in one codebase, so manual white box review can give enough coverage when all redirect operations can be assessed in limited time. Automated static analysis may struggle to tell whether input controls the start of a URL, which matters for avoiding false positives. Automated black box tools can spot Location header changes when they supply URLs to every input, but coverage is a factor and custom redirects may be missed.

A quick manual test:

  1. List every place the app issues a redirect, including login, logout, SSO callbacks, and link trackers.
  2. For each parameter that influences the target, try an absolute URL to another domain, a scheme-relative URL starting with two slashes, and encoded variants.
  3. Watch the Location header in the response.

How to fix it

From MITRE's mitigations:

  1. Use an accept-known-good approach. Keep a list of approved URLs or domains for redirection and reject anything else. Do not rely on a denylist of bad patterns alone.
  2. When the set of targets is known, map fixed IDs to the real URLs and reject all other input. ID 1 could map to the login page and ID 2 to a partner site.
  3. Where leaving your site is intended, use an intermediate page that warns the user and requires a click. Take care not to introduce cross-site scripting on that page.
  4. Require redirect requests to carry a unique, unpredictable nonce generated by the application. MITRE notes this can be bypassed using XSS.
  5. Map every untrusted input source, including cookies and hidden form fields, since many cases come from assuming certain inputs cannot be modified.
  6. An application firewall can help as an emergency measure when code cannot be fixed, with moderate effectiveness and known bypasses.
# Safer: only relative paths on this site, or an allowlist of hosts
from urllib.parse import urlparse

ALLOWED = {"app.example.com"}

def safe_target(url):
    p = urlparse(url)
    if not p.scheme and not p.netloc and url.startswith("/") and not url.startswith("//"):
        return url
    if p.scheme in ("https",) and p.hostname in ALLOWED:
        return url
    return "/"

Use the parsed host from the same library that makes the redirect, so the validator and the redirector agree on what the URL means.

Try it yourself

Run a dynamic scan against a test copy of your application and check which endpoints reflect a URL into a Location header.

Click through it at your own pace, or open it full screen.

Quick check

Why is a denylist a weak fix for open redirects? It is likely to miss at least one bad input, and encoding tricks can bypass it. An allowlist of known good targets is the stronger approach.

Is a relative redirect always safe? Not automatically. A value beginning with two slashes can be read as a different host, so check for it as well.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.