Preventing CSRF With Ajax

asp.net mvc, asp.net, code 0 comments suggest edit

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”.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

29 responses

  1. Avatar for Paul Alexander
    Paul Alexander October 10th, 2011

    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) ),

  2. Avatar for mick delaney
    mick delaney October 10th, 2011

    i think this shows how dreadful extending asp.net mvc has become... inheritance is such a brittle approach...

  3. Avatar for Kazi Manzur Rashid
    Kazi Manzur Rashid October 10th, 2011

    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?

  4. Avatar for Lee Smith
    Lee Smith October 10th, 2011

    I've been using Form tags and JQuery's serialize() to do this but I prefer your approach. Thanks

  5. Avatar for Khalid Abuhakmeh
    Khalid Abuhakmeh October 10th, 2011

    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.

  6. Avatar for Saeed Neamati
    Saeed Neamati October 10th, 2011

    "__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.

  7. Avatar for Enrique Allegretta
    Enrique Allegretta October 10th, 2011

    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.

  8. Avatar for haacked
    haacked October 10th, 2011

    @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.

  9. Avatar for counsellorben
    counsellorben October 10th, 2011

    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!

  10. Avatar for Kevin
    Kevin October 11th, 2011

    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.

  11. Avatar for haacked
    haacked October 11th, 2011

    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. :)

  12. Avatar for counsellorben
    counsellorben October 11th, 2011

    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.

  13. Avatar for haacked
    haacked October 12th, 2011

    @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.

  14. Avatar for counsellorben
    counsellorben October 12th, 2011

    (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.

  15. Avatar for Peter W
    Peter W October 13th, 2011

    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.

  16. Avatar for Riza
    Riza October 15th, 2011

    Yep. I've use in my website since MVC2 and it works great. Passing RequestVerificationToken data to $.ajax() will surely (hopefully) prevent CSRF.

  17. Avatar for John Wilander
    John Wilander October 17th, 2011

    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!

  18. Avatar for Will Vincent
    Will Vincent October 17th, 2011

    An easier way in jQuery to include the token as part of the HTTP header
    http://erlend.oftedal.no/blog/?blogid=118

  19. Avatar for Boycs
    Boycs November 2nd, 2011

    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.

  20. Avatar for joelr623
    joelr623 January 19th, 2012
  21. Avatar for Hernan
    Hernan February 29th, 2012

    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/...

  22. Avatar for Mark
    Mark March 27th, 2012

    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.

  23. Avatar for Jme
    Jme May 17th, 2012

    This doesn't seem to work. I render the token in the form as usual, grab and sent it in the ajax post (which appears to work fine - I can see it in the header in the request, and the attribute successfully moves it to the form collection, but it dies on the AntiForgery.Validate method). I copied everything just as you have it. Any ideas??

  24. Avatar for Johan Driessen
    Johan Driessen June 5th, 2012

    As you may or may not know, this doesn't work anymore for ASP.NET MVC 4 RC. As a matter fact, it is not much easier. I have blogged about and updated (and almost embarrassingly simple) version: johan.driessen.se/....

  25. Avatar for nhhr
    nhhr February 15th, 2014

    What dumb sob unit inch goat humper poste this worth piece of crapoloa
    rofl first of it is riddled with errors in mvc4. Second of all have the shit riddled with errors
    rofl just another dumb sob shit ngger homo posting crap

  26. Avatar for Matthew Wills
    Matthew Wills June 30th, 2014

    How do people solve this in the latest MVC - http://www.asp.net/mvc/over... ? Since that call no longer compiles (obsolete)?

  27. Avatar for Harry
    Harry January 9th, 2015

    MVC 5 changed the definition of the Attribute quite a bit. This is what I put together to allow getting the fields from either the header or the original form field. It ended up being a lot simpler than the original concept.

    [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 formValue = filterContext.RequestContext.HttpContext.Request.Form["__RequestVerificationToken"];
    var headerValue = filterContext.RequestContext.HttpContext.Request.Headers["__RequestVerificationToken"];
    string cookieValue = null;
    var cookieInstance = filterContext.RequestContext.HttpContext.Request.Cookies["__RequestVerificationToken"];
    if (cookieInstance != null)
    {
    cookieValue = cookieInstance.Value;
    }

    if (formValue == null)
    {
    formValue = headerValue;
    }

    AntiForgery.Validate(cookieValue, formValue);
    }
    }

  28. Avatar for Marcin Krysiak
    Marcin Krysiak April 23rd, 2015

    This might be of your interest. This code extends jQuery Ajax to include token to the defined ajax requests types that was previously get from your server.

    I successfully use that code in many projects

    the url is here: https://github.com/marcinkr...

  29. Avatar for Noor Rahman
    Noor Rahman October 22nd, 2015

    I have one question please help me
    I am working on a MVC application using repository pattern and entity
    framework. i am writing ajax call on most views for posting
    data and getting data.

    i am worried about the security , is this is the right way to use
    ajax calls on my view or do i need some extra security to secure my ajax
    call because if i open the source of html i can see all my ajax call .
    ---------------------example-------------

    $.ajax({
    url: '../User/AddGroup',
    type: "POST",
    data: {
    GroupName: $("#txtGropuName").val()
    },
    beforeSend: function () {
    blockUI("body");
    },
    success: function (data) {
    }

    any buddy can see the calls using view source.

    please help me how to seure.

    Thanks in advance.