What Is Open Redirect? URL Redirection Vulnerability Explained
A complete technical guide to open redirect vulnerabilities: how unvalidated redirect parameters let attackers craft convincing phishing links, and how to detect, verify, and prevent them.
Short answer
An open redirect occurs when an application uses attacker-controlled input to build a redirect destination, and forwards the browser there without validating that the destination is safe. The result: a link that looks like it goes to a trusted site actually sends the user somewhere else.
The idea in one minute
Picture a hotel receptionist who hands guests a card with directions to the nearest taxi stand. The card says "turn right out the door, walk one block" — trusted, official, from the hotel. Now imagine a stranger swaps the stack of cards with slightly different ones that say "turn left out the door, walk to the alley behind the building." Guests still believe the card came from the hotel, because they got it from the hotel's front desk.
That's open redirect. The "card" is a URL parameter like ?next=, ?redirect=, or ?url= that the application uses to send the user somewhere after an action. Users trust the domain in their address bar — they arrived at trusted-site.com — but they don't see the full URL before clicking, and they certainly don't inspect the redirect parameter. The attacker doesn't compromise the application; they just advertise a link that starts with the trusted domain and relies on the redirect parameter's destination being unvalidated.
How open redirect actually works
Many applications redirect users after login, logout, form submission, or language selection. A typical pattern:
GET /login?redirect=/dashboard
After successful authentication, the server reads the redirect parameter and issues an HTTP 302 or 301 response with a Location header pointing to that value. The browser follows it automatically.
When the parameter value is restricted to relative paths (/dashboard), this is safe. When the application instead accepts arbitrary absolute URLs (https://evil.com/phish) without validating them against an allowlist, it's an open redirect.
There are two common variants:
| Variant | Mechanism | Example |
|---|---|---|
| Header-based | Server reads a URL parameter or form field and uses it in the Location header of a 3xx response | redirect=//evil.com bypassing a scheme check |
| Meta-refresh / JavaScript | Server embeds the destination in a <meta http-equiv="refresh"> tag or window.location assignment | ?url=javascript:alert(1) or ?url=data:text/html,... |
Open redirects are not a server-side vulnerability in the same sense as SQL injection — the server works exactly as intended. The vulnerability is a trust boundary problem: the application's brand and domain name lend legitimacy to a destination that the application never verified.
The request flow
Attacker ──crafts link──▶ https://trusted.com/login?redirect=https://evil.com/phish
│
▼
Victim clicks the link
│
▼
Browser → GET https://trusted.com/login?redirect=https://evil.com/phish
│
▼
Server issues 302 Location: https://evil.com/phish
│
▼
Browser follows redirect to evil.com — still shows trusted.com
as the referer; user sees the URL change to evil.com only after
the redirect completes
As a sequence:
sequenceDiagram
participant Attacker
participant Victim
participant Trusted as Trusted Site
participant Evil as Attacker Site
Attacker->>Victim: Sends link with crafted redirect parameter
Victim->>Trusted: GET /login?redirect=https://evil.com/phish
Trusted-->>Victim: 302 Location: https://evil.com/phish
Victim->>Evil: GET /phish (browser follows redirect)
Evil-->>Victim: Renders phishing page (URL now shows evil.com)
A minimal example
A Flask login endpoint that redirects after authentication:
from flask import Flask, request, redirect
app = Flask(__name__)
@app.route("/login")
def login():
next_page = request.args.get("next", "/dashboard")
# ... authentication logic ...
return redirect(next_page) # unvalidated — accepts any URL
A request to /login?next=https://evil.com/phish will redirect the browser to the attacker's site. The fix is an allowlist, not a blocklist:
from urllib.parse import urlparse
ALLOWED_HOSTS = {"trusted.com", "www.trusted.com"}
ALLOWED_SCHEMES = {"https"}
ALLOWED_PATHS = {"/dashboard", "/profile", "/settings"}
def safe_redirect(target: str) -> str:
parsed = urlparse(target)
# Relative path only — safe by construction
if not parsed.netloc:
if parsed.path in ALLOWED_PATHS or parsed.path.startswith("/"):
return target
return "/dashboard"
# Absolute URL must match allowlist
if parsed.hostname in ALLOWED_HOSTS and parsed.scheme in ALLOWED_SCHEMES:
return target
return "/dashboard"
@app.route("/login")
def login():
next_page = request.args.get("next", "/dashboard")
return redirect(safe_redirect(next_page))
The safest pattern is the simplest: never accept a full URL at all. Accept only a path (/dashboard) and construct the full URL server-side. If an absolute URL is genuinely required, validate it against a strict allowlist of permitted hosts and schemes — reject everything else by default, including // scheme-relative URLs.
Why the vulnerability exists
- Redirect parameters are added for convenience (return the user to where they were) without the corresponding validation
- Applications rely on users not noticing the redirect parameter in URLs, or assume "it's just a redirect, we don't store the data" means it's low-risk
- Blocklists for "bad domains" are impossible to maintain comprehensively — every moment a new domain is registered
- Scheme-relative URLs (
//evil.com) bypass naive scheme checks that only validatehttp://orhttps:// - URL parsers differ between the validation step and the redirect step — what one parser treats as a path, another interprets as a hostname
- JavaScript-based redirects (
window.location = input) can be exploited without any server-side 3xx response at all
What attackers look for
- Any parameter that looks like it controls a destination:
next=,redirect=,return=,url=,dest=,target=,to=,logout=,referer=,done=,success=,failure= - Login, logout, and registration pages — these almost always redirect afterward, and are the most common source of open redirects
- Language/locale switchers, country redirectors, and "view this page on our mobile site" links
- URL shorteners or link-unfurling endpoints
- OAuth and SSO callback flows where the application sends the user back to a provided redirect URI
Detection
- Static/code review: search for HTTP redirect functions (
redirect(),ResponseRedirect,302,Location,window.location,location.href) and trace their inputs — any value that originates from a request parameter without passing through an allowlist is a candidate. - Dynamic/DAST: supply public, attacker-controlled URLs (or
https://[random].example.compatterns) in every redirect-shaped parameter and observe whether the browser issues a 3xx response to that external domain. - JavaScript-based: check for client-side redirects that read from
window.location.search,document.referrer, orpostMessagedata and assign the result towindow.locationorlocation.href.
Verification: real vulnerability or false positive?
A finding is confirmed when:
- The server returns a 3xx status with a
Locationheader pointing to a domain you specified, and following it in a browser actually lands on that external domain without additional user interaction - A
<meta http-equiv="refresh">tag contains attacker-controlled content and the browser auto-follows it - Client-side JavaScript reads attacker-controllable input (URL parameter, hash fragment, postMessage) and assigns it to
window.locationorlocation.href
A parameter that appears in a redirect context but is overridden by server logic (a hardcoded domain, a session-based destination) is not a finding. Confirm the value you supply actually reaches the browser as the redirect target.
Real-world impact
An open redirect alone does not compromise the hosting application. Its danger is what it enables:
- Phishing: a link to
trusted.comredirects to a near-perfect copy of the login page hosted on an attacker's domain. Users who check the URL seetrusted.comin the link preview or at the start of the address bar, and the redirect completes so fast the destination change is easy to miss. - Credential theft: chained with a login page that captures the redirect parameter, an attacker can redirect users to a fake login form after they authenticate, harvesting credentials the user believed they had already entered on the real site.
- Bypassing URL allowlists: applications that blocklist certain domains or schemes in user-generated content (comments, profile links) may accept a URL starting with
https://trusted.com?redirect=as valid — the redirect then unwinds the blocklist. - OAuth callback manipulation: if an OAuth flow accepts a user-supplied redirect URI with insufficient validation, an open redirect on the OAuth provider's domain can be used to steal authorization codes.
Open redirects are consistently ranked among the most common web vulnerabilities in industry surveys because they are trivial to find and frequently introduced by developers who consider them harmless — "it's just a redirect."
Prevention
- Never accept a full URL as a redirect target unless absolutely required. Use path-only values (e.g.,
/dashboard) and construct the full URL server-side — this eliminates the open redirect class entirely for most applications. - Use an allowlist, not a blocklist when absolute URLs are necessary. Maintain a short list of permitted hostnames and schemes, and reject anything that doesn't match. This includes rejecting scheme-relative (
//) and protocol-relative URLs. - Validate the parsed URL, not the raw string: normalize the URL before checking. Different parser behaviors between the validation step and the redirect step are a common source of bypasses — use the same parser for both, and test edge cases like trailing
@, unicode normalization, and URL-encoded characters. - Sign the redirect target when it must cross authentication boundaries: generate a cryptographic token tied to the target path, and validate the token before redirecting. The user never sees the raw target, only the token.
- Prompt the user before redirecting to an external domain (GitHub and Stack Overflow are examples of this pattern): show a warning page with the actual destination and require a click to proceed, making the redirect explicit rather than automatic.
Related vulnerabilities
- CSRF — open redirects are frequently chained with CSRF to make the forged request's consequences visible to the attacker
- XSS —
javascript:URLs in redirect parameters can be a vector for reflected XSS when the response includes the redirect target in the page - SSRF — server-side redirect following is a common bypass against naive hostname allowlists
- Clickjacking — an open redirect can be combined with a clickjack to redirect the framed page after the user interacts with it
Testing methodology (do this safely)
- Test on your own applications, or applications you have explicit authorization to test. Open redirects on third-party sites are not in scope for unauthorized testing.
- Use a domain you control to confirm that the redirect actually lands on an external destination — do not use
evil.comor other domains you don't own. - For automated testing, supply
https://[unique-subdomain].your-domain.compatterns and watch for inbound HTTP requests to confirm the redirect fired. - Document the exact URL, the parameter name, the value supplied, and the resulting
Locationheader or meta-refresh content.
Further reading
- OWASP Cheat Sheet Series: Unvalidated Redirects and Forwards Cheat Sheet
- PortSwigger Web Security Academy: Open redirect
- MITRE: CWE-601 — URL Redirection to Untrusted Site
Nyxeara perspective
Automated detection of open redirects is a high-signal, low-noise capability when the scanner sends a unique, attacker-controlled domain in every redirect-shaped parameter and waits for a corresponding HTTP callback. A response that includes a Location header pointing to the supplied domain, confirmed by an inbound callback, is evidence that the redirect is both unvalidated and functionally exploitable — not merely accepting a URL-shaped string into an unused parameter.