Skip to main content

Overview

Invalid (or unvalidated) forwards and redirects are a form of user-controlled input in which a web application accepts untrusted input that could cause the web application to redirect. Because the domain name in the modified link is identical to the trusted domain name, phishing attempts may appear more trustworthy.
This vulnerability is often combined with CSRF, man-in-the-middle attacks, or website spoofing as a more complex threat vector.

How It Works

Consider this malicious URL:
https://www.trustedwebsite.com/examples/example.php?url=http://www.malicious.com

URL Anatomy

ProtocolSubdomainDomainPathEndpointParameters
httpswwwtrustedwebsite.comexamplesexample.htmlurl=http://www.malicious.com
Users trust the URL because it starts with a legitimate domain (trustedwebsite.com), but the application redirects them to the malicious site specified in the url parameter.

Vulnerable Code Examples

The Unsecure PWA contains three open redirect vulnerabilities in main.py:
@app.route("/success.html", methods=["POST", "GET", "PUT", "PATCH", "DELETE"])
def addFeedback():
    if request.method == "GET" and request.args.get("url"):
        url = request.args.get("url", "")
        return redirect(url, code=302)  # VULNERABLE: No validation!
    if request.method == "POST":
        feedback = request.form["feedback"]
        dbHandler.insertFeedback(feedback)
        dbHandler.listFeedback()
        return render_template("/success.html", state=True, value="Back")
    else:
        dbHandler.listFeedback()
        return render_template("/success.html", state=True, value="Back")
All three endpoints accept a url parameter and redirect without any validation, allowing attackers to redirect users to any arbitrary domain.

Attack Scenarios

Phishing Attack

1

Craft Malicious Link

Attacker creates a link that appears legitimate:
https://trustedbank.com/login?url=https://evil-phishing-site.com/fake-login
2

Distribute via Phishing

Send link via email, SMS, or social media:
Subject: Urgent Security Alert

Your account requires immediate verification.
Click here: https://trustedbank.com/login?url=https://attacker.com
3

User Clicks Link

User sees trusted domain and clicks the link
4

Automatic Redirect

Application redirects user to attacker’s site without warning
5

Credential Theft

User enters credentials on fake site thinking it’s legitimate

Combined Attack Vectors

1. CSRF + Open Redirect
<img src="https://trustedsite.com/logout?url=https://attacker.com/steal-session">
2. OAuth Redirect Manipulation
https://trustedsite.com/oauth/callback?redirect_uri=https://attacker.com
3. Header Injection
https://trustedsite.com/redirect?url=https://attacker.com%0d%0aSet-Cookie:sessionid=malicious
4. JavaScript Protocol Handler
https://trustedsite.com/redirect?url=javascript:alert(document.cookie)

Penetration Testing

1

Identify Redirect Parameters

Look for URL parameters that might control redirects:
  • url, redirect, next, return, continue
  • target, dest, destination, rurl
  • page, view, goto, out
2

Test External Redirects

Test if the application accepts external URLs:
# Test various external domains
curl -I "http://127.0.0.1:5000/?url=http://www.google.com"
curl -I "http://127.0.0.1:5000/?url=https://evil.com"

# Check for 302/301 redirect to external site
3

Test Bypass Techniques

Try various encoding and bypass methods:
# URL encoding
?url=http%3A%2F%2Fevil.com

# Double encoding
?url=http%253A%252F%252Fevil.com

# Protocol-relative URL
?url=//evil.com

# @ symbol bypass
?url=http://[email protected]

# Backslash bypass
?url=http://trustedsite.com\\evil.com
4

Document Findings

Record which parameters are vulnerable and what bypass techniques work

Secure Implementation

from urllib.parse import urlparse
from flask import Flask, request, redirect, abort

app = Flask(__name__)

# Whitelist of allowed redirect domains
ALLOWED_DOMAINS = [
    'trustedsite.com',
    'www.trustedsite.com',
    'api.trustedsite.com'
]

# Whitelist of allowed internal paths
ALLOWED_PATHS = [
    '/success.html',
    '/index.html',
    '/profile',
    '/dashboard'
]

def is_safe_url(target):
    """Validate redirect URL is safe"""
    if not target:
        return False
    
    # Parse the URL
    parsed = urlparse(target)
    
    # If no scheme or netloc, it's a relative URL - validate path
    if not parsed.netloc and not parsed.scheme:
        return parsed.path in ALLOWED_PATHS
    
    # For absolute URLs, check if domain is whitelisted
    if parsed.netloc in ALLOWED_DOMAINS and parsed.scheme in ['http', 'https']:
        return True
    
    return False

@app.route("/success.html", methods=["GET"])
def addFeedback():
    if request.method == "GET" and request.args.get("url"):
        url = request.args.get("url", "")
        
        # SECURE: Validate before redirecting
        if is_safe_url(url):
            return redirect(url, code=302)
        else:
            abort(400, "Invalid redirect URL")
    
    return render_template("/success.html", state=True, value="Back")

Input Validation Approach

import re
from flask import abort, redirect

def validate_redirect_url(url):
    """Validate URL using regex patterns"""
    # Only allow relative paths starting with /
    relative_pattern = r'^/[a-zA-Z0-9/_-]+\.html$'
    
    # Or specific domain pattern
    domain_pattern = r'^https://www\.trustedsite\.com/[a-zA-Z0-9/_-]+$'
    
    if re.match(relative_pattern, url) or re.match(domain_pattern, url):
        return True
    return False

@app.route("/redirect")
def safe_redirect():
    url = request.args.get("url", "")
    
    if validate_redirect_url(url):
        return redirect(url)
    else:
        abort(400, "Invalid URL format")

Best Practices

1

Never Trust User Input

Treat all redirect parameters as untrusted user input that must be validated
2

Use Whitelists

Maintain explicit whitelists of allowed domains and paths - never use blacklists
3

Prefer Indirect References

Use mapped values instead of direct URLs:
# Instead of: ?url=/success.html
# Use: ?page=success
PAGES = {'success': '/success.html', 'home': '/index.html'}
4

Validate Protocol and Domain

Explicitly check both protocol (http/https) and domain match your whitelist
5

Warn Users

For legitimate external redirects, show a warning page:
"You are leaving trustedsite.com. Continue to example.com?"

Additional Security Measures

  1. Code Review: Audit all redirect/forward functionality
  2. Regular Expression Validation: Define strict URL format requirements
  3. Avoid URL Parameters: Use POST data or session variables when possible
  4. Content Security Policy: Implement CSP headers to limit redirect destinations
  5. User Education: Train users to verify URLs before clicking
  6. Update Backend Languages: Early versions of ASP.NET and other frameworks have known vulnerabilities

Testing Checklist

- [ ] Identify all redirect/forward parameters
- [ ] Test external domain redirects
- [ ] Test protocol handlers (javascript:, data:, file:)
- [ ] Test URL encoding bypass
- [ ] Test double encoding
- [ ] Test @ symbol bypass
- [ ] Test backslash bypass
- [ ] Test protocol-relative URLs (//evil.com)
- [ ] Test header injection
- [ ] Verify whitelist enforcement
- [ ] Check error handling for invalid URLs

Common Mistakes

DO NOT implement these vulnerable patterns:
# DON'T DO THIS - easily bypassed
blocked_domains = ['evil.com', 'malicious.net']
if any(domain in url for domain in blocked_domains):
    abort(400)
return redirect(url)  # Still vulnerable!
  • Cross-Site Request Forgery (CSRF)
  • Cross-Site Scripting (XSS)
  • Server-Side Request Forgery (SSRF)
  • OAuth redirect URI manipulation
  • HTTP Header Injection

References

Build docs developers (and LLMs) love