Preventing CSRF Hacks in ASP.NET WebAPI

Posted by: Suprotim Agarwal , on 5/3/2013, in Category ASP.NET
Views: 127086
Abstract: Use ASP.NET MVC's AntiResourceForgery token mechanism and extend it to Web API via a delegating handler to prevent CSRF attacks

Sometime back Sumit Maitra wrote a nice article about what are CSRF attacks and how to prevent them in ASP.NET MVC. He demoed how a CSRF hack can be engineered and executed in details. He also showed us how ASP.NET MVC provides the AntiRequestForgeryToken to prevent CSRF hacks.

To quickly recap, a CSRF hack involves posting data to a valid URL that often requires authentication. The hack depends on the fact that an authentication token is available in the browsers cache and when the bad URL does a post, the browser uses the cached credentials.

If we had a WebAPI backed web application, the same loop holes exist. Fact is, WebAPI with Basic Authentication enabled, the hack gets a little easier to execute because Browsers caches Basic Auth tokens to prevent prompting for authentication for each resource they are getting.

 

The solution is to have an AntiForgeryToken style implementation in Web API backed pages as well. So today we will see how we can piggy back on the MVC AntiForgeryToken implementation to thwart CSRF attacks.

A Web API service with Basic Authentication

A simple example of Basic Authentication is Windows Authentication. Today, we’ll use a Windows Authentication enabled web site to explore Cross Site Request Forgery (CSRF) risks in Web API. To do so, first create a new Web API project in Visual Studio.

Let’s call it WebApiAuthorizationToken. Select the project node in Solution Explorer and change the following properties

- Anonymous Authentication: Set it to ‘Disabled’

- Windows Authentication: Set it to ‘Enabled’

setting-windows-authentication-in-project

Now all you have to do is use the Authorize keyword on either your Controller or Action Method to protect your API call. Correct? Well yeah, kind of!

Setting up a Simple POST to a WebAPI Controller

Traditionally we use WebAPI with AJAX calls but for sake of simplicity, let’s assume we’ll make a HTTP POST that will change some value at the server. We can imagine it to be something as sensitive as Money Transfer from one account to another.

In the Index.cshtml, let’s replace the section that says “We Suggest the following” with just a markup for a Button to post an HTML Form

<div>
    @using (Html.BeginForm("", "api/Values"))
    {
        <button id="post" name="post">Transfer Money</button>
    }
</div>

As we can see, the From will by default POST to the ‘api/Values’ controller. To make our POST method ‘secure’, we decorate it with the [Authorize] attribute. To test, we simply run the application and hit the “Transfer Money” button. We put a break point in the POST method to confirm that the form is posting correctly.

secure-post-to-web-api

Confirming Security

Just to confirm the security is indeed working, we go back to the project properties and reset the authentication to Anonymous and then run the app again. You will note that instead of navigating to the Post method, we are getting a download dialog like this

image

Let us start IE Developer Tools for the Index page, go to the ‘Network’ Tab and ‘Start Capturing’.

Now click on the “Transfer Money” button. You’ll see the following response

unauthorized-exception

So as we can see, our Authorize attribute is doing the trick. We are happy that our Web API is secure… umm… kind of!

Bad Guy Site and Cross Site Posting

In the real world, the Form to post will be much more elaborate. Now let’s assume there is a “Bad Guy” who has mimicked this Form and has somehow figured out that the api/values URL handles a POST action. So now all he has to do it get you to click on a button that POSTs his data instead of yours. Even though your data is secured via Basic authentication, if you had recently logged in to the original Site, the browser will see that you are posting to a site who’s Authentication you already have and will naively send the Authentication cookie along with the Bad Request. Let’ see how this is done.

Adding the “Bad Guy Site” to the Project

Let’s add a new Web API project to the Solution and now call it the ‘BadSite’

create-bad-site

Once the project is created we add the following in the Index.cshtml (again we’ll replace the suggestions section that comes by default).

<form action="http://localhost:57320/api/Values/" method="post">

<button id="post" name="post">Transfer Money</button>

</form>

Note the port number. This post is actually going to our original Site. Now build both the solutions and launch the Original Site. Next use the IIS Express tray icon to launch the BadSite in a different web browser as well

open-bad-site-from-iise

Now you have the scary scenario of both the sites looking exactly the same (except here the Bad Guy was nice enough to change the welcome banner).

identical-sites

Click on Transfer Money from the Original Site at port number 57320. Then click on the Transfer Money from the Bad Guy’s site at 57618. In both cases you will see the breakpoint in the /api/Values Post action method is hit. YIKES! Our so called Secure and Authorized site has been hacked into by a Bad guy.

Now let’s see what we can do to thwart the baddie.

Using an AntiForgeryToken to Prevent Cross Site Posting

Adding an AntiForgeryToken generates a Cryptographically valid hash at the server end which is split and a part is added as a hidden field, whereas the rest goes into a cookie. When data is posted, the Cookie and the Hidden Field are both sent back and if they are missing or they don’t match, the POST is rejected.

In MVC this happens automatically when you request for an AntiForgeryToken. In Web API, we have to do this check manually.

Enabling AntiForgeryToken

Update the Index.cshtml to request for an AntiForgeryToken as follows

<div>
@using (Html.BeginForm("", "api/Values", FormMethod.Post))
{
  @Html.AntiForgeryToken()
  <button id="postData" name="postData">Transfer Money</button>
}
</div>

Now when this from is posted back to Server, two things happen. We peek into the Request using Fiddler to see this

request-verification-cookie

clip_image001

Notice two things

1. Cookie called __RequestVerificationToken

2. Form Data with key __RequestVerificationToken

As I mentioned above, these tokens are cryptographically significant values and are essentially matching pairs. The AntiForgery.Validate(string 1, string2) helper method actually validates the pair at the server end once the data is posted. So even if our BadGuy was able to post his set of __RequestVerificationTokens, they would fail the Validation because they were generated for a different site.

Validating the Token

To validate the token on the server side, we have to do the following

1. Retrieve the cookie

2. Retrieve the FormDataCollection

3. Pickup the values for __RequestVerificationToken from both.

4. Pass them to Validate method

5. On error throw an Unauthorized exception

The Code in the Post Action method thus becomes like this

[Authorize]
// POST api/values
public void Post([FromBody]string value)
{
CookieHeaderValue cookie = Request.Headers
  .GetCookies(AntiForgeryConfig.CookieName)
  .FirstOrDefault();
if (cookie != null)
{
  Stream requestBufferedStream = Request.Content.ReadAsStreamAsync().Result;
  requestBufferedStream.Position = 0;
  NameValueCollection myform = Request.Content.ReadAsFormDataAsync().Result;
  try
  {
   AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,
    myform[AntiForgeryConfig.CookieName]);
  }
  catch (Exception ex)
  {
   throw new HttpResponseException(
    new HttpResponseMessage(HttpStatusCode.Unauthorized));
  }
}
}

Now if the BadSite posts data to our Web API Method, they’ll get zich!

request-forgery-blocked

Generalizing the Solution using a Delegation Handler

If we wanted to generalize this solution, we could create a Delegation Handler and plug it into the Web API pipeline ensuring that all requests supply AntiForgeryToken details. Let’s quickly add a Handler and see how it works

1. Add a folder called Handlers in our Original Site

2. Add a new class called AntiForgeryTokenHandler and inherit it from DelegationHandler

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web;

namespace WebApiAuthorizationToken.Handlers
{
public class AntiForgeryTokenHandler : DelegatingHandler
{
  …
}
}

3. Next we override the SendAsync method of the DelegatingHandler.

protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
    CancellationToken cancellationToken)
{
return await base.SendAsync(request, cancellationToken);
}

4. We then move the code from the Post method to the Handler’s SendAsync Method and modify it to send HttpResponse instead of throwing Exception

protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
bool isCsrf = true;
CookieHeaderValue cookie = request.Headers
  .GetCookies(AntiForgeryConfig.CookieName)
  .FirstOrDefault();
if (cookie != null)
{
  Stream requestBufferedStream = request.Content.ReadAsStreamAsync().Result;
  requestBufferedStream.Position = 0;
  NameValueCollection myform = request.Content.ReadAsFormDataAsync().Result;
  try
  {
   AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,
    myform[AntiForgeryConfig.CookieName]);
   isCsrf = false;
  }
  catch (Exception ex)
  {
   return request.CreateResponse(HttpStatusCode.Forbidden);
   }
  }
  if (isCsrf)
  {
   return request.CreateResponse(HttpStatusCode.Forbidden);
  }
  return await base.SendAsync(request, cancellationToken);
}

5. Now if we run the application and put a break point in the Handler, we will see that our application passes through the Handler and reaches the Post method, whereas the Bad Guy’s Post is returned back from the Delegation handler itself.

request-handled-by-delegation-handler

Whereas the bad guy gets to see this

bad-guy-forbidden

Caveats and Gotchas

One always has Caveats, don’t they. Well, in our case here is a small list of important ones

1. By depending on Cookies, you are assuming all your clients will be able to present Cookies. Remember Web API services are used to support different type of clients.

2. More importantly the Key/Value generated should be successfully validated by the AntiForgery library.

3. At one point above, we have read the Request’s Content stream and reset its Position to 0.

Stream requestBufferedStream = request.Content.ReadAsStreamAsync().Result;

requestBufferedStream.Position = 0;

This works when we are using the default ‘Buffered’ content type. However, if the defaults are changed to Streaming content type, then the Stream Position cannot be reset. As a result if you read the content here, no one else will be able to read it down the pipeline.

There are multiple solutions to this, one of the is to not use the Content; instead copy the Field from the RequestBody into a header while doing an AJAX POST. Then instead of reading the content, we pick the values from the custom header and do the validation.

The AJAX call would look something like this.

$.ajax(
{
cache: false,
dataType: 'json',
type: 'GET',
headers: { "X-RVT": $('input[name="__RequestVerificationToken"]').val() },
contentType: 'application/json; charset=utf-8',
url: '/api/Values',

}

The corresponding change to the Validate method would be.

AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,
    request.Headers.GetValues("X-RVT").First());

Conclusion

We saw how we can expose a Cross Site Resource Forgery vulnerability if we were not careful with our public web services specially when using Basic Authentication. One way to plug the whole was to use ASP.NET MVC's AntiResourceForgery token mechanism and extend it to Web API via a delegating handler.

Another solution of course would be use a more robust security mechanism like OAuth.

Download the entire source code of this article (Github)

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of DotNetCurry, DNC Magazine for Developers, SQLServerCurry and DevCurry. He has also authored a couple of books 51 Recipes using jQuery with ASP.NET Controls and The Absolutely Awesome jQuery CookBook.

Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.

Get in touch with him on Twitter @suprotimagarwal or at LinkedIn



Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Nilesh Joshi on Monday, May 13, 2013 7:31 PM
Great article.. !! Thanks
Comment posted by Mike Wasson on Monday, June 3, 2013 2:12 PM
Have you see the anti-forgery handler in the MVC4 SPA template? See http://www.asp.net/single-page-application/overview/introduction/knockoutjs-template
Comment posted by Suprotim Agarwal on Saturday, July 6, 2013 12:20 AM
Mike: Thanks for the link
Nilesh: You are welcome :)
Comment posted by Hardy Wang on Wednesday, July 10, 2013 1:35 PM
I don't understand, why not just decorate the postback method with [ValidateAntiForgeryToken]?
Comment posted by Dany on Tuesday, August 20, 2013 2:41 AM
Thank you, interesting article. I would like to know if it is possible to set an expiration date for AntiForgeryToken  value (eg. Cookie token authentication requires to be changed every 2 months)
Comment posted by Mike on Friday, March 14, 2014 6:55 AM
thanks
Comment posted by Mateen on Sunday, April 19, 2015 12:51 PM
Hello

can we generate cookie token without using Razor tag?
Comment posted by nmnmn on Tuesday, May 26, 2015 3:42 PM
<script>
$(window).load(function()
{
  
   alert("(window).load was called - window is loaded!");
});  
</script>