Skip to main content

Command Palette

Search for a command to run...

ASP.NET Core: Open Redirect Vulnerability

Published
3 min read

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

  1. The Setup: You run a trusted banking site: legit-bank.com.

  2. 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=/dashboard

  3. The Attack: An attacker crafts a malicious link: https://legit-bank.com/login?returnUrl=http://fake-bank-login.com

  4. The Trap: The attacker sends this link in an email: "Please login to verify your account."

  5. The Result: The user sees legit-bank.com in the link, trusts it, and logs in. Your server then immediately redirects them to fake-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 returnUrl is /dashboard, it works. If it is http://evil.com, the framework throws an InvalidOperationException, 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.