What is Open Redirect?
An open redirect vulnerability occurs when a web application accepts a user-controlled URL parameter and redirects the browser to that URL without validation. Attackers exploit this to craft legitimate-looking links that silently forward victims to malicious sites.
Because the initial URL belongs to a trusted domain (e.g., https://your-bank.com/login?next=https://evil.com), users and security tools are less likely to flag the link as suspicious. It is commonly used as a phishing vector to steal credentials or OAuth tokens, and as a stepping stone in more complex attack chains such as SSRF bypasses.
How exploitation works
An application has a post-login redirect:
GET /login?returnUrl=https://attacker.com/fake-login HTTP/1.1
Host: trusted-site.com
The server redirects to returnUrl after authentication. The victim sees a trusted-site.com URL in their email, logs in, and is silently forwarded to a cloned login page controlled by the attacker.
OAuth flows are particularly risky: open redirects can be used to steal authorization codes by redirecting the OAuth callback to an attacker-controlled host.
Vulnerable code examples
C# / ASP.NET Core
// VULNERABLE: Redirects to any URL from query string
[HttpGet("login")]
public IActionResult Login(string returnUrl)
{
// ... authenticate user ...
return Redirect(returnUrl); // No validation — attacker controls destination
}
PHP
// VULNERABLE
$redirect = $_GET['next'];
header("Location: " . $redirect);
exit;
Secure code examples
C# / ASP.NET Core — local URL validation
// SECURE: Only redirect to relative (local) URLs
[HttpGet("login")]
public IActionResult Login(string returnUrl)
{
// ... authenticate user ...
if (Url.IsLocalUrl(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Index", "Home");
}
Python / Django — allow-list validation
from urllib.parse import urlparse
from django.conf import settings
def safe_redirect(request):
next_url = request.GET.get('next', '/')
parsed = urlparse(next_url)
# SECURE: Reject any URL with a scheme or netloc (i.e. absolute URLs)
if parsed.scheme or parsed.netloc:
next_url = '/'
return redirect(next_url)
What Offensive360 detects
- Unvalidated redirect sinks —
Response.Redirect(),header('Location:'),res.redirect(), and similar functions receiving user-controlled input - Partial validation bypasses — Allow-list checks that can be bypassed with
//evil.com,\evil.com, or protocol-relative URLs - OAuth redirect_uri abuse — Redirect URIs that accept wildcard or insufficiently validated parameters
- Meta refresh injection — User input reflected inside
<meta http-equiv="refresh" content="0;url=...">tags
Remediation guidance
-
Avoid redirecting to user-supplied URLs — The safest approach is to redirect only to known, hard-coded destinations (e.g., map a
pageparameter to a fixed URL list). -
Use
Url.IsLocalUrl()or equivalent — Framework-provided helpers correctly identify relative-only URLs and reject external destinations. -
Maintain an allow-list of approved redirect domains — If external redirects are required, check that the destination matches an explicit list of trusted domains.
-
Reject protocol-relative URLs — URLs starting with
//are treated as absolute by browsers; validate that the redirect target starts with/and does not contain://. -
Encode returnUrl in tokens — Sign redirect targets with an HMAC so they cannot be tampered with in transit.