AJAX CSRF attacks and .Net MVC - asp.net-mvc

This post describes a tokening system for all JSON HttpGet and HttpPost AJAX calls:
In short, you use the AntiForgeryToken attribute to create a token on the page, and then manually validate that that value is sent back to the controller via the AJAX call.
http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
After hours of internet sleuthing, there are always references to this possibility, but no one actually implements it. Instead the commonly repeated techniques are 1) using only HttpPost AJAX requests (which breaks REST), 2) wrapping all json responses in an anonymous object, which results in significantly inelegant code in .net MVC4, 3) using an unparsable cruf, which breaks common libraries such as backbone.js
So why aren't we all using the tokening system linked above? What am I missing?

If you're concerned about CSRF, and your concerned about REST and doing REST correctly, then you shouldn't be doing anything in a GET that would be affected by a CSRF, since the entire purpose of AntiForgeryTokens is to deal with changing data (ie, you can't use an AntiForgeryToken without first getting the page anyways that contains the token).
So, saying using POST "breaks REST" seems to be misunderstanding what you're using the token for in the first place. It's true that a GET can expose sensitive information, but you have to have some way to get the token first if you want to use it with GET.
The real problem with Ajax and AntiForgeryToken with json and Ajax is that the "built-in" validation only works with form values, not json. So you have to do the validation yourself, and that article you linked to gives a good explanation of how to do that.

Related

MVC AntiForgeryToken reused previous generated tokens

currently i'm working on ASP .NET MVC 4 application. We are using the provided [ValidateAntiForgeryToken] and the corresponding #Html.AntiForgeryToken() to generate the hidden field in our forms which are submitted using POST.
So far the mechanism seems to be working properly because if I don't provided the token as input hidden field to the target Action annotated with [ValidateAntiForgeryToken] an error is raised as expected.
However i found really strange that if i captured several generated token using Firebug or Chrome inspector, copy them into notepad and then go to a different page which also uses the AntiForgeryToken and basically replace the hidden field with any of the previous token generated, an error is not raised. I was expecting to always have a 1:1 relation (Page Hidden Field - ValidationAtServer], since if someone is able to obtain that value, will be able to forge any request to any form in the application which need the AntiForgeryToken
I was under the impression that once a token was generated it should not be possible to reuse the same token over an over, I see this a security flaw in the Framework itself.
If someone can provide more insight will be greatly appreciate it.
AntiForgeryToken is session base, so that each user has the same token but another user will have a different token. This descussion may be usefull for you: AntiForgeryToken changes per request
It's normal behaviour, because it's supposed that antiforgery token isn't compromised. If an atacker was able to compromise token, that means that atacker already has opportunity to compromise any other tokes, that would be generated. E.g. man in middle attacks.
So basically there is no need to gereate Antiforgery token per each request, and it will allow you to use already generated one for Ajax requests on current page.

AngularJS can't find XSRF-TOKEN cookie

I'm using angular 1.0.4 with an ASP.NET MVC4 + Web API project. I'm trying to leverage angular's CSRF protection to no avail. I can see that I'm passing along a cookie named XSRF-TOKEN, but when angular tries to add the value as a header named X-XSRF-TOKEN in the response, the value appears as undefined. I tried following the advice here, but the HTML has yet to render, so no element is found.
What might I be missing? Is the RequestVerificationToken cookie generated by ASP.NET MVC protected from javascript access?
Also, is it possible to have angular lazily retrieve either the cookie or form input value? If so, how? I cannot find any docs on how to do this.
I could not find an exact answer to my question. I ended up creating a service to find the 'input[name="__RequestVerificationToken"]', get its value, and return an object with that value. I then set the headers in the config using that object. This lets me lazily extract and append the value as a header for a form that doesn't appear immediately on the page.
I also realized, upon further review, that ASP.NET's AntiForgeryToken support requires both the cookie and form input/header value to validate, so the built-in AngularJS support wouldn't suffice anyway.
If anyone has a better solution, I will happily transfer the answer to that solution.

How to make WebAPI actions accessible only from my app?

A common use case for WebAPI would be to have shell views rendered by MVC controllers, which contain javascript that then hit your API to access data.
But let's say you have some expensive API operations and you don't want people remotely accessing those endpoints -- you only want your MVC views, delivered by your application, to access them. How could you go about protecting them?
In this case Request.IsLocal doesn't work, because javascript is invoking it from the client's browser on their machine. Even if it did work, you need to dig to get the real HttpContext in order to find this property -- and that solution wouldn't work in self-hosted WebAPI.
For API endpoints that require a valid IPrincipal, you could protect them with the [Authorize] attribute. But what about API endpoints that you want your app to be able to access for anonymous users?
I have tried a solution and will post it separately as an answer, because I'm not sure if it's the best (or even a good) approach.
If your MVC site uses authentication, you could enable forms authentication for your Web API methods. You could write a custom [Authorize] attribute that will check for the presence of a forms authentication cookie which will be sent from the AJAX call and if present construct the principal.
Another possible solution is to protect your API with tokens which is a more RESTful style. The idea here is that when a user authenticates on your MVC website you could generate and pass a token to the view which will be used when sending the AJAX request to the Web API which in turn will verify the validity of the token and its signature.
If on the other hand your site doesn't use authentication, then things will get very complicated because you have no way of knowing whether the request comes from a trusted client since you are using javascript to call your API methods.
Before you go harping about "what have you tried", here is what I have tried. It works. Just not sure if there is a better way.
Create an MVC action filter and add it as a global filter during Application_Start.
Create an Http (WebAPI) action filter and use it on actions that should reject remote requests.
The global MVC filter does this:
Looks for a specific cookie in the request. If the cookie is there, its value is decrypted. The decrypted value should be a string representation of a DateTime, so use DateTime.TryParse to get it out. If the value is correctly parsed to a DateTime, and that DateTime is less than a day old, STOP HERE and do nothing else.
If the cookie is not there, or cannot be decrypted / parsed, or is older than a day, write a new cookie to the browser. Use the current DateTime.UtcNow.ToString() as the value, encrypt it, and write it with HttpOnly = false.
The WebAPI filter does this:
Looks for a specific cookie in the request. If the cookie is there, decrypt its value and try to parse it out as a DateTime.
If the value is a valid DateTime and is less than 2 days old, STOP HERE and do nothing else.
Otherwise, throw a 403 Forbidden exception.
A couple of notes about my current implementation of this. First of all, I use AES encryption with a shared secret and a salt. The shared secret is stored as an appSetting in web.config. For the salt, I enabled anonymous identification and used Request.AnonymousID as the salt. I'm not entirely fond of the salt because it's tricker to get at in a WebAPI controller, but not impossible as long as it is not self-hosted.

RequestVerificationToken does not match

I have a problem with the anti CRSF MVC mechanism. The cookie and the form input returned does not match. I'm getting an error every single time, only in one specific page. In the rest of the application it works well.
The server is returning HTTP 500 Internal Server Error and I can see on the log this exception:
[System.Web.Mvc.HttpAntiForgeryException]: {"A required anti-forgery
token was not supplied or was invalid."}
This is the hidden input that the server is generating is:
<input name="__RequestVerificationToken" type="hidden" value="QK8P7rjyZE6Vm5seY7Fr704YCOoFGdTIMzl1W7R0ZFpXSMjGKLG2T05DfFSYTxvtQCEx7DDT69DGsDB2+ZXFHY8oAjiKz0gw8BhDFywgmfIpoXnGpj7fONNzIIfvbrDrE9WJsMu6Io/0bDLM5WfKs0zktiNjyOWpfYrmnfINYmjW8NLOZFoz74xTcgTptAld">
And this is the Cookie returned:
Set-Cookie:__RequestVerificationToken_L2VGbG93=skmTAVI8HCbfxDS+xhioIMIISL3UOBI7qJM1JbHjTtAqKl4W70pDUcTKMm0p3R3mrHDziE8vXw0C0OO4HArzWO1/e6py+v/cFdbe9maFgjl4jMiZ9Wc4YIhC6+IUXkk6yqJDJ8dCIr8qtGaYcD9IX+m7/SlVhu521KQSWJYRcaY=; path=/; HttpOnly
When I examine what the server is sending, the cookie is exactly the same, but the payload has different encoding I think:
__RequestVerificationToken:QK8P7rjyZE6Vm5seY7Fr704YCOoFGdTIMzl1W7R0ZFpXSMjGKLG2T05DfFSYTxvtQCEx7DDT69DGsDB2%2BZXFHY8oAjiKz0gw8BhDFywgmfIpoXnGpj7fONNzIIfvbrDrE9WJsMu6Io%2F0bDLM5WfKs0zktiNjyOWpfYrmnfINYmjW8NLOZFoz74xTcgTptAld
The differences are in two characters that appear encoded:
/ -> %2F
+ -> %2B
Those are the only differences I can find between the hidden input field, and the post payload.
What could be the problem that is causing that ValidateAntiForgeryToken fails in verify the token?
Regards.
I've had and resolved several issues with ValidateAntiForgeryToken lately, so I'll share my findings with you.
Salt: Since you mention this only happens on a single page, my best guess is that you are using different salt values in your calls to Html.AntiForgeryToken(salt) and ValidateAntiForgeryToken(salt) calls.
AJAX: as another answer has said, using AJAX may require extra work to ensure the token is included in the POST. Here is my favorite simple, automatic solution to add the token to all AJAX POST requests.
In your question though, you state that you have verified that the token is sending. Have you verified that you're only sending the token once? I found out that an AJAX call of mine was sending the token twice, which combined the values, and caused it to fail.
Machine Key and Cookies: this issue is ugly, easy to spot (causes exceptions), but not very intuitive. The validation cookies and tokens are encoded and decoded using a unique "machine key". This means that if you have a server farm, or change your server, your cookie will no longer be valid. Closing your browser fixes the issue (because the cookie is a session cookie). However, some people leave their browser windows open in the background for a long time!
The solution is to set a "machine key" in your config file. This will tell MVC to use the same key on all servers, ensuring that the cookie will be decryptable everywhere.
Encoding Bugs: using a testing utility called jMeter, we attempted to load-test our pages, only to find out that it had a bug that caused our token to have 2 extra " around the value.
The solution is to lower your trust in your tools! Test in a browser, and if that works, create a test that extracts the token and cookie values, and set a breakpoint to verify the results.
If none of these things work for you, then I'd recommend taking a look at the MVC source code for ValidateAntiForgeryTokenAttribute, specifically the OnAuthorization method. It will help you see the different steps where validation could fail. You might even inspect your error's Exception.StackTrace to determine which part is failing.
As a side note, I really dislike the implementation of ValidateAntiForgeryToken in MVC, because:
There are about 5 verification steps that can fail, but there is only one generic error message.
The class is sealed, so it cannot be extended with additional functionality.
The encryption method is weird - it initializes a Page and creates an artificial ViewState to encrypt the tokens and cookies. Seems overkill.
So, I grabbed the source code, and created my own specialized subclass, which also turned out to be very helpful in debugging its issues, because I could set breakpoints on the validation methods, and it was really easy to determine which validation step was failing.
If this is being sent as an Ajax request, then the current setup of the framework isn't build to do this naturally.
Luckly Phil Haak wrote a nice blog post on dealing with CSRF and Ajax -> Preventing CSRF With Ajax which goes into some good detail about how to use the existing framework and modify it to work for Ajax/Json.
From my recent findings ...
If you set content type as "application/x-www-form-urlencoded" in the ajax request then you must put the AFRT in the data
If you set the content type to "application/json" then the token goes in the ajax "headers" property as described by haack.
On the server if you are checking for the form type token then using the vanilla AntiForgeryRequestTokenAttribute is ok but if you want to validate tokens sent in the header then you need to call the AntiForgeryToken.OnAuthorize ... or whatever, passing the token from the cookie (http context).
It aint easy but if it was everybody would be doing it :)

asp.net mvc 2 -- losing authorization when RedirectToAction with JSON data

I'm refactoring some MVC code that originally used POST'ed form data. The form's fields are serialized using jquery's serialize() method and sent to an MVC controller Save Action that checks things out and redirects as appropriate (if errors in form values, redirect to the Edit Action, if fine then save and redirect to the Display Action). All actions are invoked via AJAX and return Partial Views. Everything works grand. Note: The site uses AD-based authorization, so users are prompted for their windows credentials upon first loading the site, but are never prompted again.
However, I'm now looking to interact with the server via JSON objects instead of form fields. Granted, I serialize the JSON object on the client and, with the aid of an imported MVC2 Futures/MVC3 class JsonValueProviderFactory, am able to correctly model bind the sent JSON object to a C# class in the Controller's parameters.
I maintain the same logic, but things start to blow up when I try to return a RedirectToAction ActionResult when the Controller accepts JSON objects. I lose authentication, the user is prompted for their credentials again, and I find myself in a infinite loop on the originally requested Action (save). Every time the user is prompted for credentials and simply runs through the Save Action again. The end result for the user is an unending alerts prompting for login credentials. Neither of the actions specified in the RedirectToAction calls are ever hit.
Can the fact that the original request uses a JSON contentType be interfering with the behavior of RedirectToAction? That's the only thing I can think of as it works fine when I don't use JSON to post and it works fine when I return PartialViews instead of using RedirectToAction. The infinite repeat of the Controller Action and continual loss of authorization credentials seems to suggest that RedirectToAction is not the way to go in this situation.
I can post code on request. I am also successfully handling stuff like copying the ModelState over to TempData and other RedirectToAction tricks. Again, it DOES work when using a non-JSON solution. Any insight is greatly appreciated!!
EDIT WITH FOLLOW-UP INFO:
Turns out, I get an "Unauthorized" error even when I completely disable NTLM authentication/authorization for the site. IIS server doesn't look for any authorization, web site doesn't look for any authorization, yet the error when trying to Redirect with JSON contentType request still occurs and complains of being "Unauthorized". This is WEIRD.
To update everyone, I haven't found a solution nor do I know for-sure what the situation is. However, I'm willing to bet it has to do with the fact that RedirectToAction issues http GET requests and the action I'm redirecting to only accepts POSTs. Even if I remove the restriction, it's still sending JSON data and it still needs to be done by POST.
In short, RedirectToAction with JSON data appears to be fundamentally undoable. You need to POST JSON data but RedirectToAction emits GET requests. That's my going theory, at least. =)

Resources