Link here

Link here

Prevent Cross-Site Request Forgery (CSRF) using ASP.NET MVC’s AntiForgeryToken() helper

ASP.NET, MVC, Security 11 Comments »

Cross-site scripting (XSS) is widely regarded as the number one security issue on the web. But since XSS gets all the limelight, few developers pay much attention to another form of attack that’s equally destructive and potentially far easier to exploit. Your application can be vulnerable to cross-site request forgery (CSRF) attacks not because you the developer did something wrong (as in, failing to encode outputs leads to XSS), but simply because of how the whole Web is designed to work. Scary!

How CSRF works

So, what’s it all about? All web application platforms are potentially vulnerable to CSRF, but in this post I’ll focus on ASP.NET MVC. Imagine you have a controller class as follows:

public class UserProfileController : Controller
{
    public ViewResult Edit() { return View(); }
 
    public ViewResult SubmitUpdate()
    {
        // Get the user's existing profile data (implementation omitted)
        ProfileData profile = GetLoggedInUserProfile();
 
        // Update the user object
        profile.EmailAddress = Request.Form["email"];
        profile.FavoriteHobby = Request.Form["hobby"];
        SaveUserProfile(profile);
 
        TempData["message"] = "Your profile was updated.";
        return View();
    }
}

This is all very normal. First, the visitor goes to Edit(), which renders some form to let them change their user profile details. Secondly, they post that form to SubmitUpdate(), which saves the changes to their profile record in the database. There’s no XSS vulnerability here. Everything’s fine, right? We implement this sort of thing all the time…

Unfortunately, this innocent controller is an easy target for CSRF. Imagine that an attacker sets up the following HTML page and hosts it on some server of their own:

<body onload="document.getElementById('fm1').submit()">
    <form id="fm1" action="http://yoursite/UserProfile/SubmitUpdate" method="post">
        <input name="email" value="hacker@somewhere.evil" />
        <input name="hobby" value="Defacing websites" />
    </form>
</body>

Next, they somehow persuade a victim to visit this page (basic social engineering, look it up). When this HTML page loads, it submits a valid form post to /UserProfile/SubmitUpdate on your server.

Assuming you’re using Windows authentication or some kind of cookie-based authentication system such as Forms Authentication, the automated form post will be processed within the victim’s established authentication context, and will successfully update the victim’s email address to something under the attacker’s control. All the attacker has to do now is use your “forgotten password” facility, and they’re taken control of the victim’s account.

Of course, instead of changing an victim’s email address, they can perform any action that the victim can perform with a single POST request. For example, they might be able to grant administrative permissions to another account, or post something defamatory to a CMS.

Ways to stop CSRF

There are two main ways to block CSRF:

  • Check that incoming requests have a Referer header referencing your domain. This will stop requests unwittingly submitted from a third-party domain. However, some people disable their browser’s Referer header for privacy reasons, and attackers can sometimes spoof that header if the victim has certain versions of Adobe Flash installed. This is a weak solution.
  • Put a user-specific token as a hidden field in legitimate forms, and check that the right value was submitted. If, for example, this token is the user’s password, then a third-party can’t forge a valid form post, because they don’t know each user’s password. However, don’t expose the user’s password this way: Instead, it’s better to use some random value (such as a GUID) which you’ve stored in the visitor’s Session collection or into a Cookie.

Using the AntiForgeryToken helpers

With Preview 5, Microsoft has added a set of helpers to the “futures” assembly, Microsoft.Web.Mvc.dll, that give you a means to detect and block CSRF using the “user-specific tokens” technique.

To use these helpers to protect a particular form, put an Html.AntiForgeryToken() into the form, e.g.,

<% using(Html.Form("UserProfile", "SubmitUpdate")) { %>
    <%= Html.AntiForgeryToken() %>
    <!-- rest of form goes here -->
<% } %>

This will output something like the following:

<form action="/UserProfile/SubmitUpdate" method="post">
    <input name="__MVC_AntiForgeryToken" type="hidden" value="saTFWpkKN0BYazFtN6c4YbZAmsEwG0srqlUqqloi/fVgeV2ciIFVmelvzwRZpArs" />
    <!-- rest of form goes here -->
</form>

At the same time, Html.AntiForgeryToken() will give the visitor a cookie called __MVC_AntiForgeryToken, with the same value as the random hidden value shown above.

Next, to validate an incoming form post, add the [ValidateAntiForgeryToken] filter to your target action method. For example,

[ValidateAntiForgeryToken]
public ViewResult SubmitUpdate()
{
    // ... etc
}

This is an authorization filter that checks that:

  • The incoming request has a cookie called __MVC_AntiForgeryToken
  • The incoming request has a Request.Form entry called __MVC_AntiForgeryToken
  • These cookie and Request.Form values match

Assuming all is well, the request goes through as normal. But if not, boom!, there’s an authorization failure with message “A required anti-forgery token was not supplied or was invalid”.

This prevents CSRF because even if a potential victim has an __MVC_AntiForgeryToken cookie, an attacker can’t find out its value, so they can’t forge a valid form post with the same value in Request.Form. But legitimate users aren’t inconvenienced at all; the mechanism is totally silent.

Using salt

Salt? What? In case you want to protect multiple forms in your application independently of each other, you can use a “salt” value when you call Html.AntiForgeryToken(), e.g.,

<%= Html.AntiForgeryToken("someArbitraryString") %>

… and also in [ValidateAntiForgeryToken], e.g.,

[ValidateAntiForgeryToken(Salt="someArbitraryString")]
public ViewResult SubmitUpdate()
{
    // ... etc
}

Salt is just an arbitrary string. A different salt value means a different anti-forgery token will be generated. This means that even if an attacker manages to get hold of a valid token somehow, they can’t reuse it in other parts of the application where a different salt value is required. (If anyone can suggest other use cases for salt, please let me know.)

Limitations of the Anti-Forgery helpers

ASP.NET MVC’s anti-CSRF helpers work very nicely, but you should be aware of a few limitations:

  • All legitimate visitors must accept cookies (otherwise, [ValidateAntiForgeryToken] will deny their form posts). Arguably this isn’t a limitation, because unless visitors allow cookies, you probably don’t have anything to protect anyway.
  • It only works with POST requests, not GET requests. Arguably this isn’t a limitation, because under the normal HTTP conventions, you shouldn’t be using GET requests for anything other than read-only operations.
  • It’s easily bypassed if you have any XSS holes on your domain. An XSS hole would allow an attacker to read a victim’s anti-forgery token value, then use it to forge valid posts. So, don’t have XSS holes!
  • It relies on the potential victim’s browser implementing cross-domain boundaries solidly. Browsers are supposed to stop foreign domains from reading your app’s response text and cookies, and are supposed to stop foreign domains from writing cookies to your domain. If an attacker manages to find a way around this, they can bypass [ValidateAntiForgeryToken]. Of course that’s not supposed to be possible. For the most part, modern browsers block this line of attack.

In conclusion, ASP.NET MVC’s anti-CSRF helpers are easy to use, and work very nicely thank you!

Using the browser’s native login prompt

ASP.NET, MVC, Security 1 Comment »

These days, every web site has its own unique “login” screen, along with its own separate system for remembering your login name and password. How many millions of developer-hours are spent designing and implementing these screens? And yet, every web browser has a built-in standard login prompt ready for you to use. For example:

image image

These have been standard in web browsers since since 1558 AD, when the legendary Duke of Login first invented the idea of logging in. Respect!

Most web developers who want to do programmatic authentication (i.e., validating credentials against Forms Authentication or directly against a database) don’t use this native browser login prompt, for two reasons:

  • It’s not clear how to make it work with programmatic authentication
  • It’s believed to be insecure (for some reason)

First, I’ll show how to use the native login prompt programmatically with ASP.NET MVC, then we’ll talk about security.

A Quick Overview of the HTTP Basic Authentication Protocol

So, how does the native login prompt actually work? What makes it appear, and what data does it send from the browser to the server?

  1. The browser makes a request to some URL
  2. The server sends back a response with an HTTP status code of 401 (meaning “Not authorized”), plus a header describing the types of authentication it will accept. For example:
        WWW-Authenticate: Basic
  3. This makes the browser display a login prompt, but it doesn’t display any other text that’s in the response. (It only displays that response text if the user clicks “Cancel”.)
  4. When the user enters some credentials, the browser resubmits the same request to the same URL, plus it also adds this extra header:
        Authorization: Basic username:password
    Note that the username:password bit is actually Base-64 encoded.
  5. The server parses the username and password from the request, and decides whether the credentials are valid or not. If they are valid, it lets the user continue (so it might return a proper HTML response, or it might redirect to somewhere else). If they are invalid, it returns a 401 again (i.e., goes back to step 2).
  6. If the user enters the same incorrect credentials twice in a row, the browser normally won’t bother resubmitting them and will just give up.

Using HTTP Basic Authentication programmatically in ASP.NET MVC

Now you know how HTTP Basic authentication works, it’s easy to use it from ASP.NET MVC. Just follow the above script, playing the role of the server.

Let’s say you want to combine Forms Authentication with a browser-native login prompt. Start by setting up Forms Authentication, i.e., put into your web.config file:

<authentication mode="Forms">
  <forms loginUrl="~/Account/Login" defaultUrl="~/">
    <credentials passwordFormat="SHA1">
      <user name="admin" password="e9fe51f94eadabf54dbf2fbbd57188b9abee436e" />
    </credentials>
  </forms>
</authentication>

Note that e9fe51… is the SHA1 hash of “mysecret”, so this configuration has a single hard-coded login name, “admin”, with password “mysecret”. In a more realistic app you’d probably not have any <credentials> in your web.config, and instead set up a membership provider to store credentials in a database. But that doesn’t change the rest of this example.

Now, assuming you’ve decorated some controller or action method with [Authorize], when the user visits that controller or action, they’ll be redirected to ~/Account/Login. To handle that request, create a new controller class called AccountController, as follows. You can replace the default implementation of AccountController if you have one.

public class AccountController : Controller
{
    public void Login()
    {
        // Ensure there's a return URL
        if (Request.QueryString["ReturnUrl"] == null)
            Response.Redirect(FormsAuthentication.LoginUrl + "?ReturnUrl=" + Server.UrlEncode(FormsAuthentication.DefaultUrl));
 
        if (TempData.ContainsKey("allowLogin"))
        {
            // See if they've supplied credentials
            string authHeader = Request.Headers["Authorization"];
            if ((authHeader != null) && (authHeader.StartsWith("Basic")))
            {
                // Parse username and password out of the HTTP headers
                authHeader = authHeader.Substring("Basic".Length).Trim();
                byte[] authHeaderBytes = Convert.FromBase64String(authHeader);
                authHeader = Encoding.UTF7.GetString(authHeaderBytes);
                string userName = authHeader.Split(':')[0];
                string password = authHeader.Split(':')[1];
 
                // Validate login attempt
                if (FormsAuthentication.Authenticate(userName, password))
                {
                    FormsAuthentication.RedirectFromLoginPage(userName, false);
                    return;
                }
            }
        }
 
        // Force the browser to pop up the login prompt
        Response.StatusCode = 401;
        Response.AppendHeader("WWW-Authenticate", "Basic");
        TempData["allowLogin"] = true;
 
        // This gets shown if they click "Cancel" to the login prompt
        Response.Write("You must log in to access this URL.");
    }
}

(By the way, I’m fully aware that the Login() action eschews a number of ASP.NET MVC best practices - it doesn’t return a useful ActionResult, and it calls Response.Redirect() directly. This makes it unsuitable for unit testing. I did this because fundamentally it’s using the static and hard-to-test FormsAuthentication API anyway. You can wrap all the static method calls inside an interface and use constructor injection, and perhaps return some special HttpBasicActionResult, if you want to make it testable - but I didn’t want to distract from the real point of this example.)

That does it! Now when a visitor goes to anything protected with [Authorize], they’ll get a browser-native login prompt, such as the one shown below. If the visitor enters valid credentials (i.e., admin/mysecret), then they’ll be given a Forms Authentication cookie, and will be redirected back to the action method they requested.

image

Notice in this screenshot that IE gives a warning about “basic authentication without a secure connection“. We’ll talk about secure connections (SSL) in a moment.

Next, you’ll want to give visitors some way of logging out. This has nothing to do with HTTP basic authentication; it’s just a matter of revoking the visitor’s Forms Authentication cookie. So, add this to AccountController:

public RedirectResult Logout()
{
    FormsAuthentication.SignOut();
    return Redirect(FormsAuthentication.DefaultUrl);
}

What a very simple way of getting a nice login UI.

Is this secure?

HTTP Basic authentication has an undeserved reputation for being insecure. Yes, it does send credentials over the wire in plain text (well, Base-64 encoded, but that’s basically the same). But then if you make a custom login form (such as the one in the default ASP.NET MVC project template), that sends credentials in plain text too. The level of security is identical.

Either way, you must protect the transmission by doing it over SSL. And that’s just as easy, or difficult, whether you use the browser’s native login prompt or create your own custom login screen.

One quirk of HTTP basic authentication is that the browser keeps on sending the Authorization header with every request that appears to be in the same folder as the one where it was originally requested. So, in this example, the browser will keep sending the Authorization header with every request to AccountController (but not to other controllers). That means you shouldn’t let the browser perform any requests to AccountController that aren’t wrapped in SSL. Also, HTTP basic authentication doesn’t give any natural way to log out, which is why I added the TempData[”allowLogin”] test so that you always get a login prompt the first time you go to Login(). When a visitor clicks “log out”, it does erase their Forms Authentication cookie, but the browser still has the credentials in its HTTP Basic cache. The user acts as logged out, but the credentials are still in the browser’s memory until they close the browser.

Conclusion

Using ASP.NET MVC, it’s easy to make a browser pop open its native login prompt, and to parse out the credentials that a user enters. These login credentials are no more or less secure than credentials entered into a normal custom login form.

However, it’s also easy to create a custom login form. This gives you more control over its appearance, and avoids the quirks of HTTP basic authentication with regard to logging out. Therefore, for most applications, it’s usually best still to create a custom login form.

This article was expanded from a short example that I was originally going to put in my forthcoming ASP.NET MVC book, but I decided to remove it from the book because it isn’t quite worthy enough…

Site Meter