title: How to avoid open-redirect vulnerability and safely redirect on successful login (HINT: ASP.NET MVC 2 default code is vulnerable)
body: <p>Normally, when a site requires that you are logged in before you can access a certain page, you are taken to the login screen and after successfully authenticating yourself, you are redirected back to the originally requested page. This is great for usability - but without careful scrutiny, this feature can easily become an <a href="http://www.owasp.org/index.php/Open_redirect" rel="noreferrer">open redirect</a> vulnerability.</p>
  
  <p>Sadly, for an example of this vulnerability, look no further than the default LogOn action provided by ASP.NET MVC 2:</p>
  
  <pre><code>[HttpPost]
  public ActionResult LogOn(LogOnModel model, string returnUrl)
  {
      if (ModelState.IsValid) {
          if (MembershipService.ValidateUser(model.UserName, model.Password)) {
              FormsService.SignIn(model.UserName, model.RememberMe);
  
              if (!String.IsNullOrEmpty(returnUrl)) {
                  return Redirect(returnUrl); // open redirect vulnerability HERE
              } else {
                  return RedirectToAction("Index", "Home");
              }
  
          } else {
              ModelState.AddModelError("", "User name or password incorrect...");
          }
      }
  
      return View(model);
  }
  </code></pre>
  
  <p>If a user is successfully authenticated, they are redirected to "returnUrl" (if it was provided via the login form submission).</p>
  
  <p>Here is a simple example attack (one of many, actually) that exploits this vulnerability:</p>
  
  <ol>
  <li>Attacker, pretending to be victim's bank, sends an email to victim containing a link, like this: <code>http://www.mybank.com/logon?returnUrl=http://www.badsite.com</code></li>
  <li>Having been taught to verify the ENTIRE domain name (e.g., google.com = GOOD, google.com.as31x.example.com = BAD), the victim knows the link is OK - there isn't any tricky sub-domain phishing going on.</li>
  <li>The victim clicks the link, sees their <em>actual</em> familiar banking website and is asked to logon</li>
  <li>Victim logs on and is subsequently redirected to <code>http://www.badsite.com</code> which is made to look exactly like victim's bank's website, so victim doesn't know he is now on a different site.</li>
  <li><code>http://www.badsite.com</code> says something like "We need to update our records - please type in some extremely personal information below: [ssn], [address], [phone number], etc."</li>
  <li>Victim, still thinking he is on his banking website, falls for the ploy and provides attacker with the information</li>
  </ol>
  
  <p>Any ideas on how to maintain this redirect-on-successful-login functionality yet avoid the open-redirect vulnerability?</p>
  
  <p>I'm leaning toward the option of splitting the "returnUrl" parameter into controller/action parts and use "RedirectToRouteResult" instead of simply "Redirect". Does this approach open any new vulnerabilities?</p>
  
  <p><strong><em>Update</em></strong></p>
  
  <p>By limiting myself to controller/action routes, I can't redirect to custom routes (e.g. <code>/backend/calendar/2010/05/21</code>). I know that by passing more parameters to the LogOn action, I <em>could</em> get it to work, but I feel like I'll always be revisiting this method -- keeping it up to date with our routing scheme. So, instead of splitting the returnUrl into its controller/action parts, I am keeping returnUrl as-is and parsing it to make sure it contains only a relative path (e.g., <code>/users/1</code>) and not an absolute path (e.g., <code>http://www.badsite.com/users/1</code>). Here is the code I'm using:</p>
  
  <pre><code>private static bool CheckRedirect(string url) {
      try {
          new Uri(url, UriKind.Relative);
      }
      catch (UriFormatException e) {
          return false;
      }
  
      return true;
  }
  </code></pre>
  
  <p><em>Side note: I know this open-redirect may not seem to be a big deal compared to the likes of XSS and CSRF, but us developers are the only thing protecting our customers from the bad guys - anything we can do to make the bad guys' job harder is a win in my book.</em></p>
  
  <p>Thanks, Brad</p>