#
Open Redirect: The Trusted Link Deception
Open Redirect vulnerabilities occur when applications accept user-controlled input to redirect users to other websites without proper validation, allowing attackers to redirect users from trusted domains to malicious sites.
In One Sentence: Tricking users into clicking legitimate-looking links on trusted websites that secretly redirect them to attacker-controlled phishing or malware sites.
Danger Level: HIGH
Open Redirect is often underestimated but highly effective in phishing campaigns, credential theft, and malware distribution.
#
What is Open Redirect?
#
In Simple Terms
Imagine you receive an email claiming to be from your bank:
Subject: Urgent: Verify Your Account
Click here to verify: https://bank.com/verify?redirect=https://evil-phishing-site.com
The link LOOKS safe because it starts with "https://bank.com"
When you click:
- You visit the real bank.com (legitimate!)
- Bank.com reads the
redirectparameter - Bank.com automatically redirects you to evil-phishing-site.com
- Evil site looks exactly like bank.com
- You enter your password thinking you're on bank.com
- Attacker steals your credentials
The Trust Problem
Users trust links that start with legitimate domains. "https://bank.com/..." looks safe, even if it redirects elsewhere.
#
Real-World Analogy: The Deceptive Concierge
Scenario: A hotel with a helpful concierge who gives directions.
Normal service:
Guest: "Where is the restaurant?"
Concierge: "Go through that door" → Points to hotel restaurant ✓
Open Redirect equivalent:
Attacker tricks concierge system:
"Send guests through door 5" → Door 5 leads OUTSIDE the hotel to a scam "restaurant"
Guest: "Where is the restaurant?"
Concierge: "Go through door 5" → Guest ends up at scammer's fake restaurant ✗
The problem: The trusted concierge (legitimate website) unknowingly helps the scammer by redirecting guests (users) to the wrong location.
#
How Open Redirect Attacks Work
#
Vulnerable Code Examples
# VULNERABLE CODE
from flask import Flask, request, redirect
@app.route('/logout')
def logout():
# Clear session
session.clear()
# Redirect to user-specified URL
next_url = request.args.get('next', '/')
return redirect(next_url) # DANGEROUS!
# Attack: /logout?next=https://evil.com
# User redirected to evil.com after logout
// VULNERABLE CODE
app.get('/redirect', (req, res) => {
const url = req.query.url;
res.redirect(url); // DANGEROUS!
});
// Attack: /redirect?url=https://malicious.com
<?php
// VULNERABLE CODE
$redirect = $_GET['redirect'];
header("Location: " . $redirect); // DANGEROUS!
exit();
// Attack: page.php?redirect=https://phishing-site.com
?>
// VULNERABLE CODE
@GetMapping("/redirect")
public String redirect(@RequestParam String url) {
return "redirect:" + url; // DANGEROUS!
}
// Attack: /redirect?url=https://attacker.com
#
Step-by-Step Attack Scenario
Setting: Online shopping site with OAuth login.
#
Normal OAuth Flow
1. User clicks "Login with Google"
2. Site redirects: https://shop.com/login?return=/checkout
3. After Google login, user redirected back to /checkout ✓
#
Open Redirect Attack
1. Attacker crafts malicious link:
https://shop.com/login?return=https://fake-shop.com/steal-password
2. Victim clicks link (trusts shop.com domain)
3. shop.com processes login
4. shop.com redirects to: https://fake-shop.com/steal-password
5. Fake site looks identical to real shop.com
6. Victim enters payment info → Stolen! ✗
Why It Works
- URL starts with legitimate domain (shop.com)
- Browser shows legitimate SSL certificate initially
- Users don't notice when redirected to different domain
- Fake site looks identical to real site
#
Types of Open Redirect Attacks
#
1. Basic URL Redirect
CRITICAL
Vulnerable pattern:
redirect_url = request.args.get('url')
return redirect(redirect_url)
Attack:
https://bank.com/redirect?url=https://phishing.com
#
2. Path-Based Redirect
HIGH
Vulnerable pattern:
redirect_path = request.args.get('path')
return redirect(redirect_path)
Attack:
# Attacker uses protocol-relative URL
https://bank.com/redirect?path=//evil.com
# Or absolute URL
https://bank.com/redirect?path=https://evil.com
#
3. Header-Based Redirect
HIGH
Vulnerable pattern:
referer = request.headers.get('Referer')
return redirect(referer)
Attack: Attacker controls Referer header.
#
4. JavaScript Redirect
MEDIUM
Vulnerable pattern:
// Client-side redirect
const url = new URLSearchParams(window.location.search).get('redirect');
window.location = url;
Attack:
https://bank.com/page?redirect=javascript:alert(document.cookie)
#
5. Meta Refresh Redirect
LOW-MEDIUM
Vulnerable pattern:
<meta http-equiv="refresh" content="0;url=<?php echo $_GET['url']; ?>">
#
Real-World Examples
#
1. Google OAuth Open Redirect (2014)
Critical Finding
Attack: OAuth redirect_uri parameter manipulation
Impact:
- Could steal OAuth tokens
- $5,000 bug bounty
- Rapid patch deployed
Vulnerable flow:
https://accounts.google.com/o/oauth2/auth?
redirect_uri=https://evil.com/steal&
client_id=...&
scope=email
After login → Redirected to evil.com with access token!
#
2. Uber Open Redirect (2017)
High Severity
Attack: riders.uber.com redirect parameter
Impact:
- Phishing campaigns targeting Uber users
- $3,000 bug bounty
- Used in credential theft
Exploit:
https://riders.uber.com/redirect?url=https://fake-uber.com
#
3. Microsoft Open Redirect (2019)
High Severity
Attack: login.live.com redirect flaw
Impact:
- Used in sophisticated phishing
- Appeared in Microsoft security bulletins
- Targeted enterprise credentials
#
How to Detect Open Redirect
#
Manual Testing
Look for URL parameters that control navigation:
?redirect=
?url=
?next=
?return=
?continue=
?dest=
?destination=
?rurl=
?out=
?view=
?to=
?returnTo=
?returnUrl=
# Test various formats
https://target.com/logout?next=https://google.com
https://target.com/logout?next=//google.com
https://target.com/logout?next=javascript:alert(1)
https://target.com/logout?next=/redirect@evil.com
https://target.com/logout?next=https://target.com.evil.com
- Does it redirect to external domain?
- Check HTTP response headers for
Location: - Test in browser to confirm actual redirect
#
Automated Testing
1. Use Burp Proxy to capture redirect requests
2. Send to Intruder
3. Set payload position: ?next=§payload§
4. Payloads:
- https://burpcollaborator.net
- //burpcollaborator.net
- javascript:alert(document.domain)
5. Check for redirects in responses
1. Spider the application
2. Active Scan with "External Redirect" policy
3. Review alerts for open redirect findings
import requests
def test_open_redirect(base_url, params):
"""Test for open redirect vulnerability"""
test_urls = [
'https://evil.com',
'//evil.com',
'javascript:alert(1)',
'/redirect@evil.com'
]
for param in params:
for test_url in test_urls:
url = f"{base_url}?{param}={test_url}"
try:
response = requests.get(url, allow_redirects=False)
# Check for redirect
if response.status_code in [301, 302, 303, 307, 308]:
location = response.headers.get('Location', '')
if 'evil.com' in location:
print(f"[!] VULNERABLE: {url}")
print(f" Redirects to: {location}")
except:
pass
# Test
test_open_redirect(
'https://target.com/logout',
['next', 'redirect', 'url', 'return']
)
#
How to Prevent Open Redirect
#
Prevention Strategy 1: Whitelist Allowed Domains
RECOMMENDED
Best Practice
Only allow redirects to known, trusted domains.
# Python/Flask - SECURE
from urllib.parse import urlparse
ALLOWED_DOMAINS = ['mysite.com', 'www.mysite.com', 'api.mysite.com']
def is_safe_redirect(url):
"""Validate redirect URL against whitelist"""
try:
parsed = urlparse(url)
# Only allow relative URLs or whitelisted domains
if not parsed.netloc: # Relative URL like /checkout
return True
if parsed.netloc in ALLOWED_DOMAINS:
return True
return False
except:
return False
@app.route('/logout')
def logout():
session.clear()
next_url = request.args.get('next', '/')
if not is_safe_redirect(next_url):
next_url = '/' # Default to safe page
return redirect(next_url)
#
Prevention Strategy 2: Use Indirect References
RECOMMENDED
Map user input to predefined safe URLs:
# Map IDs to safe destinations
REDIRECT_PAGES = {
'dashboard': '/user/dashboard',
'settings': '/user/settings',
'checkout': '/shop/checkout',
'profile': '/user/profile'
}
@app.route('/goto')
def goto():
page_id = request.args.get('page', 'dashboard')
# Look up safe destination
destination = REDIRECT_PAGES.get(page_id, '/')
return redirect(destination)
# Usage: /goto?page=checkout (SAFE)
# Attack attempt: /goto?page=https://evil.com (Fails, redirects to /)
#
Prevention Strategy 3: Validate URL Structure
DEFENSE IN DEPTH
from urllib.parse import urlparse
def validate_redirect_url(url):
"""Comprehensive URL validation"""
# Reject if empty or None
if not url:
return False
# Must start with /
if not url.startswith('/'):
return False
# Block protocol-relative URLs
if url.startswith('//'):
return False
# Block javascript: and data: protocols
if url.lower().startswith(('javascript:', 'data:', 'vbscript:')):
return False
# Block @ symbol (username@domain tricks)
if '@' in url:
return False
# Parse and validate
try:
parsed = urlparse(url)
# Must not have scheme (http://, https://)
if parsed.scheme:
return False
# Must not have network location (domain)
if parsed.netloc:
return False
return True
except:
return False
@app.route('/redirect')
def safe_redirect():
url = request.args.get('url', '/')
if not validate_redirect_url(url):
abort(400, "Invalid redirect URL")
return redirect(url)
#
Prevention Strategy 4: Warn Users
USER EXPERIENCE
For legitimate external redirects, show warning page:
@app.route('/external')
def external_redirect():
target_url = request.args.get('url')
# Validate it's intentionally external
if not is_whitelisted_partner(target_url):
# Show warning page instead of auto-redirect
return render_template(
'redirect_warning.html',
target_url=target_url
)
return redirect(target_url)
Warning page template:
<!DOCTYPE html>
<html>
<head>
<title>Leaving Our Site</title>
</head>
<body>
<h1>You are leaving our website</h1>
<p>You are being redirected to an external website:</p>
<p><strong></strong></p>
<p>Are you sure you want to continue?</p>
<a href="" rel="nofollow noopener noreferrer">
Yes, continue to external site
</a>
<a href="/">No, stay here</a>
</body>
</html>
#
Prevention Strategy 5: Framework-Specific Solutions
from django.shortcuts import redirect
from django.utils.http import url_has_allowed_host_and_scheme
ALLOWED_HOSTS = ['mysite.com', 'www.mysite.com']
def safe_redirect(request):
next_url = request.GET.get('next', '/')
# Django's built-in safe redirect check
if url_has_allowed_host_and_scheme(
url=next_url,
allowed_hosts=ALLOWED_HOSTS,
require_https=True
):
return redirect(next_url)
return redirect('/')
class SessionsController < ApplicationController
def destroy
session.clear
redirect_url = params[:next]
# Only allow relative paths
if redirect_url && redirect_url.start_with?('/')
redirect_to redirect_url
else
redirect_to root_path
end
end
end
public ActionResult Logout(string returnUrl)
{
// Sign out user
FormsAuthentication.SignOut();
// Validate redirect URL
if (!string.IsNullOrEmpty(returnUrl) &&
Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
#
Open Redirect in OAuth Flows
CRITICAL
#
The Problem
OAuth implementations often use redirect_uri parameter:
https://oauth-provider.com/authorize?
client_id=ABC123&
redirect_uri=https://client-app.com/callback&
scope=email
#
Vulnerable Implementation
# VULNERABLE OAuth callback
@app.route('/oauth/callback')
def oauth_callback():
code = request.args.get('code')
# Exchange code for token
token = exchange_code_for_token(code)
# Redirect back to client's specified URL
return_url = session.get('return_url', '/')
return redirect(return_url) # DANGEROUS!
#
Secure Implementation
# SECURE OAuth callback
@app.route('/oauth/callback')
def oauth_callback():
code = request.args.get('code')
# Validate redirect_uri matches registered URIs
redirect_uri = session.get('redirect_uri')
if not is_registered_redirect_uri(redirect_uri):
abort(400, "Invalid redirect_uri")
# Exact match required - no wildcards
if redirect_uri not in REGISTERED_REDIRECT_URIS:
abort(400, "Unregistered redirect_uri")
token = exchange_code_for_token(code)
return redirect(redirect_uri)
OAuth Best Practices
- Exact matching of redirect_uri - no wildcards
- Whitelist registered URIs per client application
- Reject unregistered URIs completely
- Use state parameter to prevent CSRF
#
Impact Scenarios
#
Scenario 1: Credential Phishing
1. Attacker sends email with open redirect link
2. User clicks: https://bank.com/redirect?url=https://fake-bank.com
3. User sees bank.com in initial URL → Trusts it
4. Redirected to fake-bank.com (looks identical)
5. User enters credentials → Stolen
#
Scenario 2: Malware Distribution
1. Open redirect used in ad campaign
2. Legitimate domain gives credibility
3. Users redirected to malware download
4. Antivirus less likely to block trusted domain
#
Scenario 3: OAuth Token Theft
1. Manipulate redirect_uri in OAuth flow
2. Steal access tokens during redirect
3. Access victim's account
#
Testing Checklist
- Identify all redirect parameters
- Test with external URLs (https://evil.com)
- Test protocol-relative URLs (//evil.com)
- Test javascript: protocol
- Test with @ symbol tricks
- Test with URL encoding
- Test with double encoding
- Test with backslash instead of forward slash
- Check OAuth redirect_uri validation
- Test meta refresh redirects
- Test JavaScript-based redirects
#
Summary: Key Takeaways
Whitelist Only allow redirects to known domains Validate Check URL structure and components Indirect Refs Map IDs to safe destinations Block External Reject absolute URLs unless whitelisted Warn Users Show confirmation page for external links OAuth Security Exact match redirect_uri in OAuth flows
#
Additional Resources
#
Tools
- Burp Suite - Includes open redirect scanner
- OWASP ZAP - Active scan for redirects
- Nuclei - Templates for open redirect testing
#
References
- OWASP Open Redirect - Complete prevention guide
- CWE-601 - URL Redirection to Untrusted Site
- OAuth 2.0 Security - Redirect URI best practices
#
Layerd AI Protection
Layerd AI Guardian Proxy prevents open redirect attacks:
URL Validation Automatic detection of external redirects Pattern Recognition ML identifies redirect manipulation attempts Real-time Blocking Prevents redirects to untrusted domains Zero Configuration Works out of the box
Learn more about Layerd AI Protection →
Last updated: November 2025