ASP.Net and CSRF

Posted by on January 7, 2013

Cross-site request forgery (CSRF) is a very common vulnerability today.  Like most frameworks, ASP.Net is not immune by default.  There are some features that are built-in that can be enabled to help reduce the surface area of this attack, however we need to be aware of how they work and what situations they may not work in.  First, lets provide a quick review of what CSRF is.

CRSF Overview

CSRF is really a result of the browser’s willingness to submit cookies to the server they are associated with.  Of course, this has to happen so that your sessions stay active and you don’t have to enter your credentials on every request.  The problem is when the request to one site (say site A) is made by a second site (say site B) without your permission or interaction.  Lets take a look at a quick example of what we are talking about. 

Say we have a site that allows deleting users by the administrator.   The administrator is provided with a table listing all the users and a link to delete each one.   That link looks something like this:

http://www.jardinesoftware.com/delete.aspx?userid=6

When an administrator clicks this link, the application first checks to see if he is authenticated (using the session or authentication cookie) and authorized.  It then calls the functions responsible to delete the user with the id of 6.

The issue here, is that there is nothing unique in this request.  If another users (without administrative rights) learns of this URL then it may be possible to get an administrator to run it for him.  Here is how that works:

  1. The attacker crafts the request he wants to be executed by the administrator.  In this case, it could be http://www.jardinesoftware.com/delete.aspx?userid=8
  2. The attacker sends the administrative email an email containing this link (probably obfuscated so they don’t realize it is calling the delete function on the website.  The attacker could also send a link to a seemingly innocent site, but contain an image tag containing a src attribute pointing to the delete link above.
  3. The victim (administrator) MUST already be logged into the application when he clicks the link. 
  4. The browser sees the request for JardineSoftware.com and kindly appends the cookies for that domain.
  5. The server receives the cookies (for authentication) and the request and processes it.  The server has no way of knowing that the user didn’t actually initiate the request on purpose.
  6. The user is deleted and the admin is none the wiser.

To resolve this, we need something to make the request unique.  When the request is unique, it is difficult for the attacker to know that unique value for the victim so the request will fail.  With the presence of XSS, many times this can be bypassed.  There are many different ways to do this, which we will cover next.  Keep in mind that each solution described below has its pros and cons that we must be aware of.

ViewState

.Net web forms have a feature called ViewState which allows storing state information on the client.  You typically don’t see this, because it is a hidden field (unless you view source).  ViewState is not guaranteed to be unique across users.  At times, there may be unique values in there that we don’t think about (the user name), but for the most part, ViewState is not sufficient to protect against CSRF.  Enter ViewStateUserKey.  ViewStateUserKey provides a unique value within the Viewstate per user session.  ViewStateUserKey is not enabled by default, and must be set by the developer.  This property can be set in the Page_Init event on each page or in the master page.  The following is an example of how this can be set:

protected void Page_Init(object sender, EventArgs e)
{
        Page.ViewStateUserKey = Session.SessionId;
}

Microsoft has made some changes in Visual Studio 2012.  If you create a new Web Forms application, it will include some additional CSRF changes to help mitigate the issue out of the box.  The following shows an example of the new Master Page Init method (non-relevant code has been removed):

protected void Page_Init(object sender, EventArgs e)
{
    // The code below helps to protect against XSRF attacks
    var requestCookie = Request.Cookies[AntiXsrfTokenKey];

    if (requestCookie != null && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
    {
        // Use the Anti-XSRF token from the cookie
        _antiXsrfTokenValue = requestCookie.Value;
        Page.ViewStateUserKey = _antiXsrfTokenValue;
    }
    else
    {
        // Generate a new Anti-XSRF token and save to the cookie
        _antiXsrfTokenValue = Guid.NewGuid().ToString("N");
        Page.ViewStateUserKey = _antiXsrfTokenValue;
    }
}

In the code above, we can see that the ViewStateUserKey is now being set in the Master Page by default.  What a great addition.  So what are the limits here?

For starters, and hopefully this is obvious, this technique doesn’t work on requests that don’t use ViewState.  Remember the example we used earlier in the post?  There is no ViewState there, so this doesn’t offer any protection for that situation.  There are also some other situations that could lead to this not working.  In .Net 2.0, with EventValidation disabled, ViewStateUserKey would not get validated if the ViewState is empty.   I have discussed the ability to pass an empty viewstate before, and this is one of the perks.  Many times, ViewState may be present, but the developers do not need it for the processing of the request.   If we pass it as __VIEWSTATE=   with no content then in 2.0, ViewStateUserKey will not get checked.   This was changed in .Net 4.0 where the framework now checks if ViewStateUserKey was set and will check it even if the ViewState is empty.  This doesn’t effect requests that don’t use ViewState like the example above.

Nonce or Anti-Forgery Token

Another technique that can be used to protect requests from CSRF is what is called a ‘Nonce’.  A Nonce is a single use token that gets included with every request.  This token is only known to the user and changes for each request.  The idea is that only the requestor of the page with have a valid token to submit the action.  In our example above, a new parameter would need to exist such as this:

http://www.jardinesoftware.com/delete.aspx?userid=6&antiforgery=GJ38r4Elke7823SERw

Yes, that value for the antiforgery parameter is just made up.  It should be random so it is not guessable by an attacker.  This would limit an attacker’s ability to know what YOUR request is to the resource.  This is a great way to mitigate CSRF, but can be tricky to implement.  ASP.Net MVC has built in functionality for this.  For Web forms, you either have to build it, or you can look to OWASP at their CSRFGuard project.   I am not sure how stable it is for .Net, but it could be a good starting point.

In Visual Studio 2012, the default WebForm application template attempts to add anti-csrf functionality in to the master page.  The following code snippet shows some of the code that does this. (Code has been removed that is not relevant and there is supporting code that is not present from other functions):

protected void Page_Init(object sender, EventArgs e)
{
    // The code below helps to protect against XSRF attacks
    var requestCookie = Request.Cookies[AntiXsrfTokenKey];
    Guid requestCookieGuidValue;
    if (requestCookie != null && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
    {
        // Use the Anti-XSRF token from the cookie
        _antiXsrfTokenValue = requestCookie.Value;
    }
    else
    {
        // Generate a new Anti-XSRF token and save to the cookie
        _antiXsrfTokenValue = Guid.NewGuid().ToString("N");

        var responseCookie = new HttpCookie(AntiXsrfTokenKey)
        {
            HttpOnly = true,
            Value = _antiXsrfTokenValue
        };
        if (FormsAuthentication.RequireSSL && Request.IsSecureConnection)
        {
            responseCookie.Secure = true;
        }
        Response.Cookies.Set(responseCookie);
    }
}

In the code above, we can see the anti-csrf value being generated and stored in the cookies collection.  Not shown, the anti-csrf value is also stored in the ViewState as well.  This does show that Microsoft is making an attempt to help developer’s protect their applications by providing default implementations like this.

Require Credentials

Another technique that is often employed is to require the user to re-enter their credentials before performing a sensitive transaction.  In the example above, when the administrator clicked the link, he would need to enter his password before the delete occurred.  This is a fairly effective approach because the attacker doesn’t know the administrator’s password (I hope).  The downside to this technique is that users may get upset if they constantly have to re-enter their credentials.  By placing too much burden on the users, they may decide to use something else. This must be used sparingly. 

CAPTCHA

CAPTCHAs can also be used to help protect against CSRF.  Again, this is a unique value per user that the attacker should not know.  Like the Credentials solution, it does require more work by the user.  Check out Rafal Los’ post on Is unusable the same as ‘secure’? Why security is borked.  where he points out the difficulties with a CAPTCHA system when human’s can’t read them.

Use POST Requests

This is not really a mitigation, but more of a recommendation.  CSRF can be performed on POST requests too.  I just wanted to mention this here to cover it, but this really doesn’t have much weight.  All an attacker needs to do is get the victim to visit a page with a hidden form containing the attack request and use JavaScript to auto-submit that form behind the scenes.

Check the Referrer

This is similar to the use of POST requests.  It can be bypassed with the proper technologies and isn’t a full solution.  This is another item that needs to be implemented properly.  I have seen situations where this has been implemented on pages that were ok to access directly and it caused issues. 

Conclusion

As you can see, there are many different ways, including more than listed, to protect against CSRF.  Microsoft has implemented some nice new changes into the default Visual Studio 2012 Web Form template to help protect against CSRF by default.  It is important to understand what the implementation is and its limits of protection.  Without this understanding it is easy to overlook a situation where your application could be vulnerable.

Comments

Comments are closed.