Open Redirect Vulnerabilities: Why Your Login Page Might Be Phishing Your Users
Open redirects are the most underrated vulnerability class in web security. They're consistently deprioritised in triage ("it's just a redirect"), they're excluded from many bug bounty scopes, and they're rarely flagged by automated scanners. And yet they're the foundation of some of the most effective phishing campaigns and the key ingredient in SSRF bypass chains.
Here's the problem in one URL:
https://your-bank.com/login?next=https://attacker.com/fake-login
Your user sees your-bank.com in the link. They click it. They land on a page that looks exactly like your login form. They enter their credentials. The credentials go to the attacker.
The redirect happened on your domain. Your SSL certificate was in the address bar. Your brand was in the URL. And your application sent the user to the attacker.
Where Open Redirects Hide
Open redirects appear wherever an application uses a URL parameter to control navigation:
| Location | Common Parameters | Impact |
|---|---|---|
| Login pages | next, redirect, return_to, continue | CRITICAL โ credential phishing |
| OAuth callbacks | redirect_uri, callback | CRITICAL โ token theft |
| Logout pages | redirect, next | MEDIUM โ phishing after logout |
| Marketing links | url, dest, target | MEDIUM โ brand abuse |
| Error pages | return, back | LOW โ limited phishing |
| File downloads | file, path, download | MEDIUM โ malware delivery |
The Attack Chains
Chain 1: Phishing via Trusted Domain
The simplest and most effective use. The attacker crafts a URL using your domain that redirects to their phishing page. Email filters and link scanners see your trusted domain and let the link through. The user sees your domain and trusts it.
https://company.com/login?redirect=https://company-login.attacker.com
Chain 2: OAuth Token Theft
OAuth flows use a redirect_uri parameter to send the authorization code back to the application. If the OAuth provider doesn't strictly validate the redirect URI, an attacker can change it to their own server and steal the authorization code.
https://oauth-provider.com/authorize?
client_id=legit-app&
redirect_uri=https://legit-app.com/callback/../../../redirect?url=https://attacker.com&
response_type=code
The authorization code โ which can be exchanged for an access token โ goes to the attacker.
Chain 3: SSRF Bypass
Many SSRF defences validate the URL before making the request: "only allow requests to api.example.com." An open redirect on api.example.com bypasses this:
# SSRF payload that passes URL validation
GET /fetch?url=https://api.example.com/redirect?next=http://169.254.169.254/latest/meta-data/
The URL validation sees api.example.com (allowed). The server follows the redirect to the metadata endpoint (not allowed, but the validation already passed).
Chain 4: XSS via JavaScript Protocol
If the redirect is implemented client-side (via window.location = param), the attacker can use the javascript: protocol:
https://app.com/redirect?url=javascript:alert(document.cookie)
This turns an open redirect into a reflected XSS vulnerability.
How to Test for Open Redirects
Manual Testing
# Test 1: External domain redirect
curl -s -o /dev/null -w "%{redirect_url}" \
"https://target.com/login?next=https://attacker.com"
# Test 2: Protocol-relative URL (bypasses scheme checks)
curl -s -o /dev/null -w "%{redirect_url}" \
"https://target.com/login?next=//attacker.com"
# Test 3: Backslash confusion (some parsers treat \ as /)
curl -s -o /dev/null -w "%{redirect_url}" \
"https://target.com/login?next=https://attacker.com\@target.com"
# Test 4: URL encoding bypass
curl -s -o /dev/null -w "%{redirect_url}" \
"https://target.com/login?next=https%3A%2F%2Fattacker.com"
# Test 5: JavaScript protocol (for client-side redirects)
# Check if the page sets window.location to the parameter value
Common Bypass Techniques
| Bypass | Payload | What It Bypasses |
|---|---|---|
| Protocol-relative | //attacker.com | Scheme validation (checks for http/https) |
| Backslash | https://attacker.com\@target.com | Domain validation (some parsers read target.com) |
| URL encoding | %2F%2Fattacker.com | Literal string matching |
| Subdomain | https://target.com.attacker.com | Suffix-based domain validation |
| Tab/newline | https://attacker%09.com | Whitespace-insensitive parsers |
| Data URI | data:text/html,<script>...</script> | Scheme allowlists that don't include data: |
How to Fix Open Redirects
Option 1: Allowlist (Best)
ALLOWED_REDIRECTS = {'/dashboard', '/profile', '/settings'}
def safe_redirect(url):
if url in ALLOWED_REDIRECTS:
return redirect(url)
return redirect('/dashboard') # Default fallback
Option 2: Same-Origin Only
from urllib.parse import urlparse
def safe_redirect(url):
parsed = urlparse(url)
# Only allow relative paths (no scheme, no host)
if parsed.scheme or parsed.netloc:
return redirect('/dashboard')
# Block protocol-relative URLs
if url.startswith('//'):
return redirect('/dashboard')
return redirect(url)
Option 3: Signed Redirect Tokens
For cases where you need to redirect to external URLs (e.g., marketing links), use signed tokens instead of raw URLs:
# Generate: /redirect?token=hmac_signed_url
# Verify: check HMAC before redirecting
# This prevents attackers from crafting arbitrary redirect URLs
What NOT to Do
- Don't use denylists โ blocking "attacker.com" doesn't block "attacker.org" or "attacker.com.evil.com"
- Don't check the scheme only โ
//attacker.comhas no scheme but still redirects - Don't check the domain suffix โ
target.com.attacker.comends with "target.com" but is attacker-controlled - Don't use client-side redirects with user input โ
window.location = paramenables javascript: protocol XSS
Testing Checklist
| # | Check | Expected Result |
|---|---|---|
| 1 | External URL in redirect parameter | Redirect blocked or goes to default page |
| 2 | Protocol-relative URL (//attacker.com) | Redirect blocked |
| 3 | Backslash confusion (https://evil\@target.com) | Redirect blocked |
| 4 | URL-encoded payload | Redirect blocked after decoding |
| 5 | Subdomain confusion (target.com.evil.com) | Redirect blocked |
| 6 | JavaScript protocol (javascript:alert(1)) | Not executed |
| 7 | Data URI (data:text/html,...) | Redirect blocked |
| 8 | OAuth redirect_uri manipulation | Strict redirect_uri validation by OAuth provider |
Bottom Line
Open redirects are the vulnerability that makes other vulnerabilities worse. On their own, they enable phishing. Combined with SSRF, they bypass URL validation. Combined with OAuth, they steal tokens. Combined with XSS, they execute arbitrary JavaScript.
The fix is simple: don't redirect to user-controlled URLs. Use allowlists for internal paths, signed tokens for external URLs, and never trust a URL parameter to be safe just because it starts with your domain.
See also: SSRF Detection (open redirects as SSRF bypass), XSS Detection (javascript: protocol via redirect), CORS Misconfiguration (redirect-based CORS bypass).