CWE 601: Open Redirects

Flaw

CWE 601: Open Redirects are security weaknesses that allow attackers to use your site to redirect users to malicious sites. Because your trusted domain is in the link, your organization’s reputation could be damaged or it could lend legitimacy to a phishing campaign that steals credentials from your users.

For example:

public class AccountController : Controller
{
    ...
    public ActionResult Login(string username, string password, string returnUrl)
    {
        if (AuthenticateUser(username, password))
        {
            return Redirect(returnUrl);
        }
        else
        {
            ModelState.AddModelError("Auth", "Authentication failed");
            return View();
        }
    }
    ...
}

This code allows an application to redirect the browser to a provided URL after the user has successfully logged in. For example, if the application is on trustworthy.org, a URL like https://www.trustworthy.org/account/login?returnUrl=stealyourmoney.org eventually redirects the browser to a page on stealyourmoney.org.

Server-side code defines the returnUrl variable as a value taken from the user's inbound request, then passes that value to the ControllerBase.Redirect() method, to be used as its target location. After the user has successfully logged into the system this controller sends them to the location specified in the URL's single parameter. Web applications frequently use this approach for legitimate redirects. Without some constraints on allowed values for the returnUrl parameter, an attacker can use it to deceive other users by tricking them into visiting an untrustworthy site.

Unlike many other flaws related to data validation, the risk is not that the attacker will send malformed data; the attack actually requires well-formed data to succeed.

For example, an application hosted on a trusted domain: trustworthy.org. The attacker hosts a near-identical clone of this site at stealyourmoney.org. The login pages of the two sites are visually indistinguishable, but the attacker controls the stealyourmoney.org application, and retains all input sent to it. The attackers want to trick users into logging onto their site instead, to steal their credentials.

Some visitors would turn back after noticing the suspicious domain name in the URL, but a less-experienced user (or one in a hurry) might only see the trustworthy.org domain and assume the site is safe.

The attacker's goal is to use this redirect method on trustworthy.org to direct a user unknowingly to their stealyourmoney.org site. They construct a malicious URL:

https://www.trustworthy.org/account/login?returnUrl=stealyourmoney.org

To hide the stealyourmoney.org domain information from more-attentive users, the attacker URL-encodes the domain name, which then looks like this:

https://www.trustworthy.org/account/login?returnUrl=%73%74%65%61%6C%79%6F%75%72%6D%6F%6E%65%79%2E%6F%72%67

This link can be included in an email message as part of a phishing campaign.

Your account is expiring, and you'll lose access to this service. Log in at

https://www.trustworthy.org/account/login?returnUrl=%73%74%65%61%6C%79%6F%75%72%6D%6F%6E%65%79%2E%6F%72%67

to keep your account active.

This text attracts attention and creates a sense of urgency based on fear, so it is more likely users click the link in context. When clicked, despite having started on trustworthy.org, the user lands on stealyourmoney.org. When users are worried, they are less likely to notice the site redirected them, especially as the stealyourmoney.org website looks just like trustworthy.orgone. The new site asks them to authenticate again, and when they do, the attacker captures their credentials. The attackers have taken the trust that the users have in trustworthy.org and use it against them.

Fix

There are two possible ways to fix an Open Redirect issue in your website.

  1. Indirect references
  2. IsLocalUrl validation

Indirect references

The client controls the returnUrl parameter, so an attacker can also control the parameter. Therefore, the code must ensure that any URL it receives is safe. One of the most-reliable ways to do this is to create a table of allowed URLs, and have the returnUrl parameter only contain an integer that serves as an index to those allowed URLs.

public class AccountController : Controller
 {
     ...
-    public ActionResult Login(string username, string password, string returnUrl)
+    public ActionResult Login(string username, string password, int urlID)
     {
         if (AuthenticateUser(username, password))
         {
-            return Redirect(returnUrl);
+            Dictionary<int, string> allowed = GetAllowedUrls();
+            if (allowed.TryGetValue(urlID, out string url))
+            {
+                return Redirect(url);
+            }
+            else
+            {
+                return View("Index");
+            }
         }
         else
         {
view fixed code only

In this example, the code's behavior changes to allow any targets from a group of destinations that are defined in advance on the server side. Instead of permitting access to any URL that a user provides, the new function uses a list of all permissible target URLs (a whitelist), then assigns an indirect value to each target. These indirect values now appear in the URL instead of the literal destination. A valid link, such as http://www.trustworthy.org/#/redirect?url=5, sends the user to the URL represented by the index 5. If the attacker tries to repeat their exploit, the URL http://www.trustworthy.org/#/redirect?url=stealyourmoney.org does not perform the redirect. Doing it this way prevents the attacker from tricking someone into visiting any URL that they type.

UrlHelper.IsLocalUrl() Validation

If the redirect is only allowed to any URL that is local to the webserver there is an alternative way to fix the problem. For ASP.NET MVC there is a System.Web.Mvc.UrlHelper class available that has an API called IsLocalUrl(string url) that validates if the URL is local. You can safely redirect if the result of that method is true. The code is similar to the following:

{
         if (AuthenticateUser(username, password))
         {
-            return Redirect(returnUrl);
+            if (Url.IsLocalUrl(returnUrl))
+            {
+                return Redirect(returnUrl);
+            }
+            else
+            {
+                return View("Index");
+            }
         }
         else
         {
view fixed code only

References

CWE ↪ WASC ↪

Ask the Community

Ask the Community