ASP.NET Core: Open Redirect Vulnerability
An Open Redirect occurs when a web application accepts a user-controlled input (often a URL parameter like ?returnUrl=...) and redirects the user's browser to that URL without validating it.
While this doesn't directly compromise the server, it is a powerful tool for Phishing attacks. It allows attackers to leverage your domain's reputation to trick users into visiting malicious sites.
Real-World Scenario
The Setup: You run a trusted banking site:
legit-bank.com.The Login Logic: Your login page often has a parameter to send users back to where they came from after logging in:
https://legit-bank.com/login?returnUrl=/dashboardThe Attack: An attacker crafts a malicious link:
https://legit-bank.com/login?returnUrl=http://fake-bank-login.comThe Trap: The attacker sends this link in an email: "Please login to verify your account."
The Result: The user sees
legit-bank.comin the link, trusts it, and logs in. Your server then immediately redirects them tofake-bank-login.com(which looks exactly like your site), where they unwittingly enter their credit card details again.
Vulnerable Code Example (.NET)
This is a classic mistake in older MVC or Web API controllers:
[HttpPost]
public IActionResult Login(LoginModel model, string returnUrl)
{
if (IsValidUser(model))
{
// VULNERABLE: No check on where 'returnUrl' goes
return Redirect(returnUrl);
}
return View();
}
If returnUrl is http://evil.com, the browser obeys and leaves your site.
How to Prevent It in .NET Core
The goal is to ensure that the redirect URL is Local (belonging to your own application) and not an absolute URL pointing to an external domain.
1. The Native Solution: LocalRedirect
.NET Core provides a specific helper method for this exact purpose. Instead of Redirect(), use LocalRedirect().
[HttpPost]
public IActionResult Login(LoginModel model, string returnUrl)
{
if (IsValidUser(model))
{
// SAFE: Throws an exception if the URL is not local
return LocalRedirect(returnUrl);
}
return View();
}
- Behavior: If
returnUrlis/dashboard, it works. If it ishttp://evil.com, the framework throws anInvalidOperationException, stopping the attack.
2. Manual Validation: Url.IsLocalUrl
If you need custom logic (e.g., you don't want to throw an exception but instead redirect to a default home page), check the URL first.
[HttpPost]
public IActionResult Login(LoginModel model, string returnUrl)
{
if (IsValidUser(model))
{
// Check if the URL belongs to this domain
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
// Fallback to a safe default
return RedirectToAction("Index", "Home");
}
}
return View();
}
Advanced: Allowing Specific External Redirects
Sometimes you do need to redirect externally (e.g., to a payment gateway like PayPal or a sister site). In this case, use an Allow-List.
Do NOT just check if the string starts with http.
public IActionResult GoToPartner(string url)
{
var allowedDomains = newList<string> { "paypal.com", "mysistersite.com" };
// Parse the URL to check the host safely
if (Uri.TryCreate(url, UriKind.Absolute, out var uriResult))
{
if (allowedDomains.Contains(uriResult.Host))
{
return Redirect(url);
}
}
return BadRequest("Invalid redirect destination");
}
Summary Checklist
Audit: Search your code for
Redirect(string)and ensure the input isn't user-controlled.Fix: Replace insecure redirects with
LocalRedirect(string).Fallback: Always have a default "safe" redirect (like Home/Index) if the validation fails.