Documentation

# Open Redirect: The Trusted Link Deception

Open Redirect Illustration
Open Redirect Illustration

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.


# 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:

  1. You visit the real bank.com (legitimate!)
  2. Bank.com reads the redirect parameter
  3. Bank.com automatically redirects you to evil-phishing-site.com
  4. Evil site looks exactly like bank.com
  5. You enter your password thinking you're on bank.com
  6. Attacker steals your credentials

# 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! ✗

# 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

# 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)

# 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