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 :)
Related
I have to implement a web site (MVC4/Single Page Application + knockout + Web.API) and I've been reading tons of articles and forums but I still can't figure out about some points in security/authentication and the way to go forward when securing the login page and the Web.API.
The site will run totally under SSL. Once the user logs on the first time, he/she will get an email with a link to confirm the register process. Password and a “salt” value will be stored encrypted in database, with no possibility to get password decrypted back. The API will be used just for this application.
I have some questions that I need to answer before to go any further:
Which method will be the best for my application in terms of security: Basic/ SimpleMembership? Any other possibilities?
The object Principal/IPrincipal is to be used just with Basic Authentication?
As far as I know, if I use SimpleMembership, because of the use of cookies, is this not breaking the RESTful paradigm? So if I build a REST Web.API, shouldn't I avoid to use SimpleMembership?
I was checking ThinkTecture.IdentityModel, with tokens. Is this a type of authentication like Basic, or Forms, or Auth, or it's something that can be added to the other authentication types?
Thank you.
Most likely this question will be closed as too localized. Even then, I will put in a few pointers. This is not an answer, but the comments section would be too small for this.
What method and how you authenticate is totally up to your subsystem. There is no one way that will work the best for everyone. A SPA is no different that any other application. You still will be giving access to certain resources based on authentication. That could be APIs, with a custom Authorization attribute, could be a header value, token based, who knows! Whatever you think is best.
I suggest you read more on this to understand how this works.
Use of cookies in no way states that it breaks REST. You will find ton of articles on this specific item itself. Cookies will be passed with your request, just the way you pass any specific information that the server needs in order for it to give you data. If sending cookies breaks REST, then sending parameters to your API should break REST too!
Now, a very common approach (and by no means the ONE AND ALL approach), is the use of a token based system for SPA. The reason though many, the easiest to explain would be that, your services (Web API or whatever) could be hosted separately and your client is working as CORS client. In which case, you authenticate in whatever form you choose, create a secure token and send it back to the client and every resource that needs an authenticated user, is checked against the token. The token will be sent as part of your header with every request. No token would result in a simple 401 (Unauthorized) or a invalid token could result in a 403 (Forbidden).
No one says an SPA needs to be all static HTML, with data binding, it could as well be your MVC site returning partials being loaded (something I have done in the past). As far as working with just HTML and JS (Durandal specifically), there are ways to secure even the client app. Ultimately, lock down the data from the server and route the client to the login screen the moment you receive a 401/403.
If your concern is more in the terms of XSS or request forging, there are ways to prevent that even with just HTML and JS (though not as easy as dropping anti-forgery token with MVC).
My two cents.
If you do "direct" authentication - meaning you can validate the passwords directly - you can use Basic Authentication.
I wrote about it here:
http://leastprivilege.com/2013/04/22/web-api-security-basic-authentication-with-thinktecture-identitymodel-authenticationhandler/
In addition you can consider using session tokens to get rid of the password on the client:
http://leastprivilege.com/2012/06/19/session-token-support-for-asp-net-web-api/
We have an ASP.NET MVC 2 (.NET 4) application running on Windows Azure (latest 2.x OS version) with two web role instances.
We use the anti-forgery token supplied by MVC for all POST requests, and we have set a static Machine Key in web.config, so everything works on multiple machines and across restarts. 99.9% of the cases it works perfectly.
Every now and then, however, we log a HttpAntiForgeryException, with message "A required anti-forgery token was not supplied or was invalid."
I know the problem might be cookies not being allowed in the browser, but we've verified that and cookies are enabled and being sent back and forth correctly.
The error occurs with a variety of browsers and obviously causes problems to the users because they have to repeat the operation or they can lose some data. Suffice it to say, we haven't been able to reproduce the problem locally, but it only happens on Windows Azure.
Why is that happening? How can we avoid it?
I ran into this recently as well and found two causes.
1. Browser restores last session on open for page that is cached
If you have a page that is cachable that performs a post to your server (i.e. antiforgery will be on) and the user has their browser set to restore last session on start up (this option exists in chrome) the page will be rendered from cache. However, the request verification cookie will not be there because it is a browser session cookie and is discarded when browser is closed. Since the cookie is gone you get the anti-forgery exception. Solution: Return response headers so that the page is not cached (i.e. Cache-Control:private, no-store).
2. Race condition if opening more than one tab on start up to your site
Browsers have the option to open a set of tabs at start up. If more than one of these hit your site that returns a request verification cookie you can hit a race condition where the request verification cookie is overwritten. This happens because more than one request hits your server from a user that does not have the request verification cookie set. The first request is handled and sets the request verification cookie. Next the second request is handled, but it did not send the cookie (had not been set yet at request time) so the server generates a new one. The new one overwrites the first one and now that page will get an antiforgery request exception when it next performs a post. The MVC framework does not handle this scenario. This bug has been reported to the MVC team at Microsoft.
The anti forgery token contains the username of the currently connected user when it is emitted. And when verifying its validity, the currently connected user is checked against the one used when the token was emitted. So for example if you have a form in which the user is not yet authenticated and you emit an anti forgery token, there won't be any username stored in it. If when you submit the form you authenticate the user, then the token will no longer be valid. Same applies for logging out.
Here's how the Validate method looks like:
public void Validate(HttpContextBase context, string salt)
{
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
string str2 = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);
HttpCookie cookie = context.Request.Cookies[str2];
if ((cookie == null) || string.IsNullOrEmpty(cookie.Value))
{
throw CreateValidationException();
}
AntiForgeryData data = this.Serializer.Deserialize(cookie.Value);
string str3 = context.Request.Form[antiForgeryTokenName];
if (string.IsNullOrEmpty(str3))
{
throw CreateValidationException();
}
AntiForgeryData data2 = this.Serializer.Deserialize(str3);
if (!string.Equals(data.Value, data2.Value, StringComparison.Ordinal))
{
throw CreateValidationException();
}
string username = AntiForgeryData.GetUsername(context.User);
if (!string.Equals(data2.Username, username, StringComparison.OrdinalIgnoreCase))
{
throw CreateValidationException();
}
if (!string.Equals(salt ?? string.Empty, data2.Salt, StringComparison.Ordinal))
{
throw CreateValidationException();
}
}
One possible way to debug this is to recompile ASP.NET MVC from its source code and log exactly in which of the if cases you enter when the exception is thrown.
I have a few MVC3 web apps that get this pretty regularly also. The majority of them are because the client doesn't send a POST body. And most of these are IE8 because of some bug with ajax requests preceding a regular form post. There's a hotfix for IE that seems to address the symptoms, which sort of proves that it is a client bug in these cases
http://support.microsoft.com/?kbid=831167
There are a few discussions about the issue around the web, nothing too useful though, I definitely am not about to mess with keep-alive timeouts which is a suggested "solution" in some places...
https://www.google.com/search?q=ie8+empty+post+body
I've never been able to reproduce it with a variety of attempts to reset connections between POSTS so I'm afraid I don't have a real solution for the case of the IE empty POST bodies. The way we've mitigated it a little bit is to make sure that we never use the POST method when just retrieving data via ajax.
If you log the full request, check to see if the POST body is empty, and if it is, it'll probably be an older IE. And I don't mean Content-Length: 0, it will usually have a Content-Length that seems correct in the headers but there will literally be nothing after the headers in the request.
The issue as a whole is still a mystery to me though because we still get the occasional exception where there is a complete POST body. Our usernames never change and our keys are static as well, I haven't tried adding debugging to the source, if I ever get around to that I will report my findings.
There are a couple of options for what you could try. You could try remoting into the machine and looking at the event log to see if you can get more information from that in regards to where this is happening. If that doesn't help, you can use DebugDiag or some other tool to capture a dump of the process (DebugDiag will let you capture one at the time of this specific exception). And then look at that to see what is going on.
If you can't seem to figure it out from there, you can always create a support case with Microsoft to help you investigate it.
I have encountered similar problems with my home-brewed anti-forgery code, which is conceptually very similar to the MVC mechanism. Mostly the problem seems to occur because modern browsers appear willing to display cached copies of pages specified as non-cached.
I have tried all combinations of page no-cache directives, but sometimes I still get cached pages displayed.
I have found that a better solution is to hook the onbeforeunload event for the page and explicitly clear the value of the hidden input field holding the token value in the DOM.
If a cached copy of a page is loaded, it seems to contain the cleared input field value. I then test for this in the document ready function and reload the page if necessary:
window.location.reload(true);
Seems to work quite effectively, and I suspect it might for the MVC anti-forgery code too.
Yes others have asked similar questions, however, the end solution was using JavaScript. I have that working, my question becomes what happens when the user has JavaScript turned off? I would hope only advanced users would be turning off JavaScript and thus know to know click once on a button and can tell that the server is working. On the off chance they don't, how do I make sure that button is only clicked once?
I should note that this is on a payment form.
I'm afraid without JavaScript there is no way to prevent this. If the click results in a POST request, then you can try to make it idempotent on the server side.
You cannot make sure the button is only clicked once, as you have no control over user's browser. What you can do, though, is to add a hidden field, a token to your forms so that if you see a token you've already seen, you'll be able to return an already-calculated answer.
Update: In case of payment processing, it's not even a technique for preventing double submission—it's a technique protecting your clients from fraud. Check out OWASP's A5: Cross-Site Request Forgery (CSRF):
Preventing CSRF requires the inclusion of a unpredictable token in the body or URL of each HTTP request. Such tokens should at a minimum be unique per user session, but can also be unique per request.
The preferred option is to include the unique token in a hidden field. This causes the value to be sent in the body of the HTTP request, avoiding its inclusion in the URL, which is subject to exposure.
The unique token can also be included in the URL itself, or a URL parameter. However, such placement runs the risk that the URL will be exposed to an attacker, thus compromising the secret token.
Basically, each time you receive a payment form, you want to make sure it's a legitimate response to the form you've shown. Handling double submission comes free with security—a rare case indeed! ;)
what happens when the user has JavaScript turned off?
The server is hit twice and there is not much you could do about it.
Now depending on what you are doing on the server there are different ways to react. For example in a RESTful application if you are using a POST verb which modifies some state on the server and is neither safe nor idempotent it is eventually the underlying data source that will detect the anomaly and simply throw an exception which will be gracefully reported to the user telling him that his request was already submitted.
For a simple and small ASP.NET MVC app, I find using the HttpRuntime.Cache is enough:
Function SomeAction() As ActionResult
Dim cachekey = "uniquecode"
If HttpRuntime.Cache(cachekey) IsNot Nothing Then
' wait until the other request is finished
Do
Threading.Thread.Sleep(1000)
Loop Until HttpRuntime.Cache(cachekey) Is Nothing
End If
HttpRuntime.Cache.Add(
cachekey, "", Nothing,
DateTime.Now.AddMinutes(5),
Cache.NoSlidingExpiration, CacheItemPriority.Low, Nothing)
Try
' do stuff
Finally
HttpRuntime.Cache.Remove(cachekey)
End Try
End Function
The cachekey must be the same for requests that you consider double, and must be different from all the other requests. If you check that the cachekey is already in the cache, tell the thread that is processing the request to wait (or throw error).
I use DotnetOpenAuth, i need to pass some data with SetCallbackArgument, but the problem is that some openId provider such as
http://pip.verisignlabs.com/
http://clickpass.com/public/username
redirect to my site with query string more than 2048 character and IIS can't handle it. I got 404 - File or directory not found. error cause that they redirect is too big.
What should I do in this case?
The OpenID spec states that OpenID responses that are too large should use form POST instead of 301 redirects which helps to avoid this problem. I don't know if verisign or clickpass is neglecting that part of the spec or not. But in general your callback arguments should be kept short to avoid this kind of problem. And certainly if your callback argument is very large it could never be expected to work since the callback arguments must always be in the query string.
Typically if you want a large bit of data available when the user returns, you can store it in a db, in the user session, or in a cookie, and only store a small reference to that data in the callback argument.
Remember that anything in the callback argument is subject to inspection by the user, any third party if HTTPS isn't used for the whole thing, and even possible tampering. DNOA provides optional tampering protection for callback arguments, but not confidentiality.
I have read the section on header injections as described here: http://guides.rubyonrails.org/security.html. But I can't seem to walk through a step by step example of this in my head. Could someone walk me through an example of how exploiting the referer header could cause issues in an application?
It is very simple:
A malicious user can insert due to a vulnerability in earlier versions of Ruby and RoR a secuence of URL encoded characters “%0d%0a” that are the equivalent for “\r\n” which is a carriage-return and line-feed.
In this way a new lines in the header can be injected with new information as cookies, redirections, referers and any other information that can be used to help the attacker to commit his purpose.
As example maybe the one in the link you sent is not exactly the best, but think about a cookie validation to access a private site. Some sites use to locate a cookie to a value like "true" or "1" once the user pass trough the validation process. If you insert into the header the cookie value without passing the validation process you should access the private pages without the need of login into the application.