The shape of the bug
A URL parameter controls where the application sends the user next. The application redirects to whatever the parameter says.
Now the attacker crafts:
The user clicks this link — from a phishing email, from a malicious tweet, from a comment posted somewhere. They see bank.com in the URL, log in successfully, and the bank redirects them to bank-secure-login.attacker.com. The attacker's site looks identical to bank.com's account page. The user enters their second-factor code. Game over.
The attacker never compromised the bank. They just used the bank as a redirect amplifier.
Where it hides
- Login flows:
?next=,?return_to=,?redirect=,?back=,?continue=,?destination=. - Logout flows: "after logout, return to..."
- Password reset: the link in the email contains a return URL.
- OAuth and SAML callback URLs: the
redirect_uriparameter. A separate, related bug class — see the OAuth redirect attack lab. - Marketing tracking links:
https://example.com/track?u=https://destination.com. Common in email campaigns. Attacker uses your tracking link to wrap a malicious destination. - HTML
<meta http-equiv="refresh">with attacker-controlled URL. - JavaScript
window.location = userInput— client-side open redirect, often via URL fragment.
Bypass tricks
Developers add naive checks. Attackers route around them.
| Naive check | Bypass |
|---|---|
startsWith("/") | //evil.com/path — protocol-relative URL. Browser treats as https://evil.com/path. |
contains("yoursite.com") | https://evil.com/login?fake=yoursite.com or https://yoursite.com.evil.com. |
endsWith("yoursite.com") | https://evil.com/yoursite.com. |
| Blocklist of known bad domains | Use any domain not on the list. There are infinitely many. |
Allow only yoursite.com subdomains | Subdomain takeover — if you have an abandoned DNS record pointing at S3/Heroku/etc and the attacker can claim it, they get a *.yoursite.com redirect target. |
startsWith("https://yoursite.com") | https://yoursite.com.evil.com — the check passes because yoursite.com.evil.com starts with the right string. |
The right way
Pattern 1: allow only relative paths
Often the entire feature is just "send me back to the page I was on." That page is always on your site. So accept only relative paths starting with /, and refuse anything containing ://, //, or scheme-like characters.
Pattern 2: allow-list of full URLs
If the feature requires redirecting to specific external partners, maintain an allow-list. Compare the parsed hostname exactly — not a substring match.
Pattern 3: indirection token
Sometimes you need to support arbitrary redirect destinations (marketing tracking links, search results). Don't redirect from a URL parameter. Instead, store the destination server-side and let the user pass a token that looks it up.
Why open redirect matters
By itself, open redirect causes no direct technical damage — it doesn't read data, doesn't execute code, doesn't break authentication. CVSS scores it low. Many bug bounty programs explicitly mark it as out of scope.
But it is the single most effective accelerator for phishing campaigns. When the phishing URL starts with https://your-bank.com/login?, the user's mental model says "this is my bank." Email security filters that check the URL domain see your bank's domain and let it through. URL preview features in chat apps show your bank's title and favicon. The attacker has borrowed your reputation.
In combination with other bugs, it gets worse:
- OAuth flows: open redirect on the callback URL lets the attacker steal authorization codes (see the redirect_uri attack).
- Token-bearing redirects: if your redirect URL includes a session token or password reset token in the query string, the attacker's server captures it directly via Referer header.
- SSO chains: open redirects in one app become login-bypass tools for federated apps that trust it.
The takeaway
Open redirect is the politest serious vulnerability. The application "just does what you ask." But "what you ask" can be a phishing trampoline, an OAuth token thief, or a reputation laundering service.
The fix is structural: don't pass full URLs in redirect parameters. Pass relative paths only, or use an allow-list, or use server-side indirection tokens. Every login page, every password reset, every OAuth callback, every logout flow — audit each one.
References
Formatted in APA 7.
- OWASP. (2024). Unvalidated redirects and forwards cheat sheet. OWASP Cheat Sheet Series. https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
- MITRE. (2024). CWE-601: URL redirection to untrusted site ('open redirect'). Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/601.html
- PortSwigger. (2024). Open redirection vulnerabilities. Web Security Academy. https://portswigger.net/kb/issues/00500100_open-redirection-reflected