Preventing CSRF With Ajax

A long while ago I wrote about the potential dangers of Cross-site Request Forgery attacks, also known as CSRF or XSRF. These exploits are a form of confused deputy attack.

police-academyScreen grab from The Police Academy movie.In that post, I covered how ASP.NET MVC includes a set of anti-forgery helpers to help mitigate such exploits. The helpers include an HTML helper meant to be called in the form that renders a hidden input, and an attribute applied to the controller action to protect. These helpers work great when in a typical HTML form post to an action method scenario.

But what if your HTML page posts JSON data to an action instead of posting a form? How do these helpers help in that case?

You can try to apply the ValidateAntiForgeryTokenAttribute attribute to an action method, but it will fail every time if you try to post JSON encoded data to the action method. On one hand, the most secure action possible is one that rejects every request. On the other hand, that’s a lousy user experience.

The problem lies in the fact that the under the hood, deep within the call stack, the attribute peeks into the Request.Form collection to grab the anti-forgery token. But when you post JSON encoded data, there is no form collection to speak of. We hope to fix this at some point and with a more flexible set of anti-forgery helpers. But for the moment, we’re stuck with this.

This problem became evident to me after I wrote a proof-of-concept library to  ASP.NET MVC action methods from JavaScript in an easy manner. The JavaScript helpers I wrote post JSON to action methods in order to call the actions. So I set out to fix this in my CodeHaacks project.

There are two parts we need to tackle this problem. The first part is on the client-side where we need to generate and send the token to the server. To generate the token, I just use the existing @Html.AntiForgeryToken helper in the view. A little bit of jQuery code grabs the value of that token.

var token = $('input[name=""__RequestVerificationToken""]').val();

That’s easy. Now that I have the value, I just need a way to post it to the server. I choose to add it to the request headers. In vanilla jQuery (mmmm, vanilla), that looks similar to:

var headers = {};
// other headers omitted
headers['__RequestVerificationToken'] = token;

$.ajax({
  cache: false,
  dataType: 'json',
  type: 'POST',
  headers: headers,
  data: window.JSON.stringify(obj),
  contentType: 'application/json; charset=utf-8',
  url: '/some-url'
});

Ok, so far so good. This will generate the token in the browser and send it to the server, but we have a problem here. As I mentioned earlier, the existing attribute which validates the token on the server won’t look in the header. It only looks in the form collection. Uh oh! It’s Haacking time! I’ll write a custom attribute called ValidateJsonAntiForgeryTokenAttribute.

This attribute will call into the underlying anti-forgery code, but we need to get around that form collection issue I mentioned earlier.

Peeking into Reflector, I looked at the implementation of the regular attribute and followed its call stack. It took me deep into the bowels of the System.Web.WebPages.dll assembly, which contains a method with the following signature that does the actual work to validate the token:

public void Validate(HttpContextBase context, string salt);

Score! The method takes in an instance of type HttpContextBase, which is an abstract base class. That means we can can intercept that call and provide our own instance of HttpContextBase to validate the anti-forgery token. Yes, I provide a forgery of the request to enable the anti-forgery helper to work. Ironic, eh?

Here’s the custom implementation of the HttpContextBase class. I wrote it as a private inner class to the attribute.

private class JsonAntiForgeryHttpContextWrapper : HttpContextWrapper {
  readonly HttpRequestBase _request;
  public JsonAntiForgeryHttpContextWrapper(HttpContext httpContext)
    : base(httpContext) {
    _request = new JsonAntiForgeryHttpRequestWrapper(httpContext.Request);
  }

  public override HttpRequestBase Request {
    get {
      return _request;
    }
  }
}

private class JsonAntiForgeryHttpRequestWrapper : HttpRequestWrapper {
  readonly NameValueCollection _form;

  public JsonAntiForgeryHttpRequestWrapper(HttpRequest request)
    : base(request) {
    _form = new NameValueCollection(request.Form);
    if (request.Headers["__RequestVerificationToken"] != null) {
      _form["__RequestVerificationToken"] 
        = request.Headers["__RequestVerificationToken"];
    }
}

  public override NameValueCollection Form {
    get {
      return _form;
    }
  }
}

In general, you can get into all sorts of trouble when you hack around with the http context. But in this case, I’ve implemented a wrapper for a tightly constrained scenario that defers to default implementation for most things. The only thing I override is the request form. As you can see, I copy the form into a new NameValueCollection instance and if there is a request verification token in the header, I copy that value in the form too. I then use this modified collection as the Form collection.

Simple, but effective.

The custom attribute follows the basic implementation pattern of the regular attribute, but uses these new wrappers.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, 
    AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute : 
    FilterAttribute, IAuthorizationFilter {
  public void OnAuthorization(AuthorizationContext filterContext) {
    if (filterContext == null) {
      throw new ArgumentNullException("filterContext");
    }

    var httpContext = new JsonAntiForgeryHttpContextWrapper(HttpContext.Current);
    AntiForgery.Validate(httpContext, Salt ?? string.Empty);
  }

  public string Salt {
    get;
    set;
  }
  
  // The private context classes go here
}

With that in place, I can now decorate action methods with this new attribute and it will work in both scenarios, whether I post a form or post JSON data. I updated the client script library for calling action methods to accept a second parameter, includeAntiForgeryToken, which causes it to add the anti-forgery token to the headers.

As always, the source code is up on Github with a sample application that demonstrates usage of this technique and the assembly is in NuGet with the package id “MvcHaack.Ajax”.

What others have said

Requesting Gravatar... Paul Alexander Oct 10, 2011 2:59 PM
# re: Preventing CSRF With Ajax
The attribute implementation is interesting and useful for potentially other issues, but why not just add the token directly to the values submitted instead of adding a custom header?

data: window.JSON.stringify($.extend( obj, headers) ),

Requesting Gravatar... mick delaney Oct 10, 2011 4:38 PM
# re: Preventing CSRF With Ajax
i think this shows how dreadful extending asp.net mvc has become... inheritance is such a brittle approach...
Requesting Gravatar... Kazi Manzur Rashid Oct 10, 2011 7:58 PM
# re: Preventing CSRF With Ajax
Two Questions:?

1. Why not put the token in the html meta like rails and use the jQuery ajaxSetup/Send to auto inject the value instead of doing the same for each form?

2. How does it would work when you need to support both adaptive rendering or more specifically ajax will be used when js is turned on and regular from post when off?
Requesting Gravatar... Lee Smith Oct 10, 2011 8:08 PM
# re: Preventing CSRF With Ajax
I've been using Form tags and JQuery's serialize() to do this but I prefer your approach. Thanks
Requesting Gravatar... Khalid Abuhakmeh Oct 10, 2011 8:28 PM
# re: Preventing CSRF With Ajax
I ran into this same problem yesterday which is funny, but I started realizing that recovering from a successful/failed AJAX request is really the issue and passing the antiforgery token is the trivial part.

What do you do when the AJAX request is complete and the user is left on the page? I thought the token had a one time use, which makes every subsequent ajax request from a single page invalid, unless you can generate a token an pass it back on every JSON request.

Also the Html.AntiForgeryToken() helper assumes you always want an input html tag. What if you just want to set the token cookie and return a new token?

I like what Kazi suggested, by adopting the view wide forgery token, this could be alleviated and by convention, the MVC ajax libraries could just update it if it exists on the page to a new value.
Requesting Gravatar... Saeed Neamati Oct 10, 2011 11:58 PM
# re: Preventing CSRF With Ajax
"__RequestVerificationToken"? What a long ugly id it is. Still one of those hardcoded id and names of ASP.NET generated HTML. The more I use ASP.NET's generated code, the more I find myself stuck at customizing a project furthermore. I'd rather prefer to write the anti-forgery system on my own (just like membership, SSO, etc.).

Your article helps me in finding where I need to implement my own framework. Thanks.
Requesting Gravatar... Enrique Allegretta Oct 11, 2011 12:28 AM
# re: Preventing CSRF With Ajax
Hi Phil, I think it would be better to create an attribute that support both scenarios, inside you should check if the request is ajax and do this new kind of black magic, otherwise you should leave it as it is.

Thx.
Requesting Gravatar... haacked Oct 11, 2011 1:17 AM
# re: Preventing CSRF With Ajax
@Paul because then I'd need to deserialize the JSON payload twice, once in the authorization filter and again for the action method. I didn't want to do that.

@Kazi 1. We could consider that. 2. Should work fine.

@Saeed a long name reduced the chance of conflict with fields developers might be using in their apps. :)

@Enrique, the attribute I wrote would work in both scenarios.
Requesting Gravatar... counsellorben Oct 11, 2011 2:23 AM
# re: Preventing CSRF With Ajax
Having bitched about this security issue, and being unsatisfied with the various approaches I have seen and employed to date to address this, I find your approach a stronger defensive strategy, and I will plug it into my applications.

Thanks!
Requesting Gravatar... Kevin Oct 11, 2011 3:00 AM
# re: Preventing CSRF With Ajax
Wow, snarkiness! The criticism may be valid but you guys could at least try to make it constructive. The framework never prevented you from doing this yourself, and it didn't take a whole lot of effort to implement.

Phil, thanks for taking the time to code this up and provide it to all of us freeloaders. Some of us appreciate it.

Requesting Gravatar... haacked Oct 11, 2011 5:09 AM
# re: Preventing CSRF With Ajax
As some folks on Twitter pointed out, since the XmlHttpRequest object in browsers don't allow cross-domain posts, for most cases, what I've done here isn't necessary.

However, there have been plugins in the past that allowed cross-domain posts (but not GETs). So consider this a defense in depth approach. Most of the time, you probably don't need it.

I'll try and write a follow-up blog post that talks about the real risk of this attack and whether or not these protections are really needed. :)
Requesting Gravatar... counsellorben Oct 11, 2011 9:30 AM
# re: Preventing CSRF With Ajax
Phil,

I disagree with those folks on Twitter. It is correct that the XmlHttpRequest object does not allow cross-domain posting, but the analysis cannot end at this point.

MVC is agnostic as to how it receives a request. A malicious user can submit a x-www-form-urlencoded request masquerading as a JSON request, and it will be accepted by MVC and processed as if it were a JSON request, unless a developer inspects the request to confirm it truly is a JSON request.

Until such time as MVC includes built-in methods to inspect and confirm that an AJAX or JSON request is not really a x-www-form-urlencoded in masquerade, I will use inspectors and AntiForgeryTokens whenever possible.

Thanks again for this code, since it provides defense in depth.
Requesting Gravatar... haacked Oct 12, 2011 4:29 AM
# re: Preventing CSRF With Ajax
@counsellorben The Mvc3Futures project (and NuGet package), has an AjaxOnlyAttribute you can use which checks the header for X-Requested-With=XMLHttpRequest. You could use that to determine a real ajax request.
Requesting Gravatar... counsellorben Oct 12, 2011 3:47 PM
# re: Preventing CSRF With Ajax
(Sigh!) If only that was sufficient. Alas, checking for X-Requested-With=XMLHttpRequest is insufficient. I took my earlier proof of concept CSRF attack based on your bank demo, and updated it to use jquery-1.5.1.js.

I then changed one line in jquery-1.5.1, and I successfully submitted a cross-domain x-www-form-urlencoded POST request containing the X-Requested-With=XMLHttpRequest header, which was processed by the controller action.

I changed another line, and I made a successful JSON cross-domain POST request.

The only line of defense against these attacks is an AntiForgeryToken.

If you would like my updated proof-of-concept, email me, and I will email it to you.
Requesting Gravatar... Peter W Oct 14, 2011 2:37 AM
# re: Preventing CSRF With Ajax
Justin Etheredge encountered the same "Form" limitation back in Feb 2011 when he had to deal against CSRF in AJAX requests:
www.codethinked.com
We needed the same capability, and are eagerly awaiting an update or additional helpers that you referred to.
Requesting Gravatar... Riza Oct 15, 2011 10:46 AM
# re: Preventing CSRF With Ajax
Yep. I've use in my website since MVC2 and it works great. Passing RequestVerificationToken data to $.ajax() will surely (hopefully) prevent CSRF.
Requesting Gravatar... John Wilander Oct 17, 2011 11:49 PM
# re: Preventing CSRF With Ajax
counsellorben: Did yo manage to do a cross-domain ajax call without abusing a vulnerable Flash proxy? If so, please email me the PoC at john dot wilander at owasp dot org. Thx!
Requesting Gravatar... Will Vincent Oct 18, 2011 1:13 AM
# re: Preventing CSRF With Ajax
An easier way in jQuery to include the token as part of the HTTP header
http://erlend.oftedal.no/blog/?blogid=118
Requesting Gravatar... Boycs Nov 02, 2011 2:03 PM
# re: Preventing CSRF With Ajax
Thanks Phil, this was exactly what I was after.

I modified my implementation slightly to add a jQuery ajaxPrefilter to append the token on the client side, and used your AuthFilter globally and added:

if(filterContext.RequestContext.HttpContext.Request.HttpMethod.ToLower() != "post")
{
return;
}

to target all form Post's...

This seems to work pretty nicely.
Requesting Gravatar... joelr623 Jan 19, 2012 7:10 PM
# re: Preventing CSRF With Ajax
See http://awesome.codeplex.com/
Requesting Gravatar... Hernan Feb 29, 2012 2:14 PM
# re: Preventing CSRF With Ajax
This help help to resolve my problem! I have this problem "Preventing CSRF With Ajax" (haacked.com/.../preventing-csrf-with-ajax.aspx) but this is fired before ValidateInput(false) so I replace _form = new NameValueCollection(request.Form); with _form = new NameValueCollection(request.Unvalidated().Form);

stackoverflow.com/...
Requesting Gravatar... Mark Mar 27, 2012 4:28 PM
# re: Preventing CSRF With Ajax
Looks interesting... But how are you able to all AntiForgery.Validate with the httpcontext?.. from what I can se that overloaded method is marked as internal.

What do you have to say?

(will show your gravatar)
Please add 5 and 1 and type the answer here: