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.
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.
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 domainscurl -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
from urllib.parse import urlparsefrom flask import Flask, request, redirect, abortapp = Flask(__name__)# Whitelist of allowed redirect domainsALLOWED_DOMAINS = [ 'trustedsite.com', 'www.trustedsite.com', 'api.trustedsite.com']# Whitelist of allowed internal pathsALLOWED_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")
# DON'T DO THIS - easily bypassedblocked_domains = ['evil.com', 'malicious.net']if any(domain in url for domain in blocked_domains): abort(400)return redirect(url) # Still vulnerable!