JSON users: Avoid CSRFs by not using top-level arrays
flask.pocoo.org
flask.pocoo.org
[Cross Site Request Forgery] vulnerabilities occur when a website allows an authenticated user to perform a sensitive action but does not verify that the user herself is invoking that action. The key to understanding CSRF attacks is to recognize that websites typically don't verify that a request came from an authorized user. Instead they verify only that the request came from the browser of an authorized user. Because browsers run code sent by multiple sites, there is a danger that one site will (unbeknownst to the user) send a request to a second site, and the second site will mistakenly think that the user authorized the request.
From: http://freedom-to-tinker.com/blog/wzeller/popular-websites-v...
Via: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and...
It works by updating the token (timestamp) on the server and in the cookie on each request and they both have to match on the next request. A short buffer (say 30 seconds) is permitted to avoid false positives from click-happy users on slow/flaky connections.
Has anyone used this technique in production?
I believe the best practice is to provide the user's session id in their cookie and provide that with the requests via javascript or even at the time of page rendering. Since a third party (untrusted domain) cannot access information from your cookie, they also cannot form a valid request.
This method is called double submitting cookies.
See: https://secure.wikimedia.org/wikipedia/en/wiki/Cross-site_re...
Although this makes sense, I feel it is not as safe as double submitting cookies, which effectively creates pseudo-random requests, but should still be safe enough in practice.
Since the browser automatically submits the cookie via HTTP Headers, single submission by itself is not safe. Since a third party cannot read the value of the cookie, they cannot recreate the proper request and will, consequently, fail.
Of course, both our methods will fail under an XSS, but should still prevent CSRF. I still think a cryptographically generated secret stored in the cookie is less guessable than a timestamp.
The Microsoft asp.net web stack avoids this by automatically wrapping json responses with an object "{d:...}".
The comments describe this as only affecting FF2.0 although testing was informal. (You should, of course, still protect your services.)
ASP.NET ASMX Web Services, WCF Web Services, and the now-defunct ASP.NET AJAX automatically wrap with {'d':...}.
ASP.NET MVC does not.
Phil explains that the reason there is a difference is that with MVC there is no common client library that automatically strips the {'d':...} wrapper. So they felt it would be too confusing for users. http://haacked.com/archive/2009/06/25/json-hijacking.aspx
That was an old article so maybe they have changed the default behavior since then. In any case, you can manually wrap the response with {'d':...}.
http://jeremiahgrossman.blogspot.com/2006/01/advanced-web-at...
tl;dr Sometimes ugly is elegant, too.
[1] http://perl.plover.com/yak/12views/samples/notes.html#sl-3
They started with trying to fix a performance problem I didn't have, and then after my refusal to give them more information to fix a problem I didn't have, started insulting me.
Edit:
Come to think of it, this seems to be solvable by a much better method - the same one that is used to prevent standard CSRF. The problem is effectively the same.
Server A is a valid server. User logs into it and gains privileges. Then he visits server B, which is a bad server. B tricks the browser into sending a request to server A to do something (abusing the elevated privileges). The only addition with AJAX is that server B also manages to read the result of its attack. That wouldn't matter if it couldn't trick the server A honoring that page request in the first place.
You can easily solve this by signing your requests, effectively binding two pages on your server together. Normally, PAGE1 has a form (or JS code) that requests PAGE2. You simply need to enforce that only legal (yours) pages can do that. This is achieved by adding a token to PAGE1 that must be sent to PAGE2.
For example, token = hash(server_secret + PAGE2_identifier + user_id).
Upon receiving a request for PAGE2, the server will know all the arguments that went into creating that token, and re-generated. If the user-supplied token and server-generated tokens don't match, the request is denied.
As long as client side scripts from server B cannot read the token from server A, this should work even with AJAX requests. Since attacker (server B) cannot know server_secret, they will not be able to guess your generated token, and their requests to PAGE2 on server A will fail.
EDIT: This is the correct idea. Search for shieldlen or safeResponse in the JS to see how it is implemented. There is always something interesting to be learned by digging through FB's client side code.
By eval()ing your json you are doing most of the attackers work for them. All that stuff in the article about mime types is redundant if you're eval()ing your json.
Basically, don't ever eval() anything, in any language.
From: http://www.json.org/js.html
"The use of eval is indicated when the source is trusted and competent. It is much safer to use a JSON parser."
FB is still using eval() if you look at their code. As the source of the JSON is their own service, and they can, therefore, trust it assuming proper sanitization; the same applies for my test case.
However, in this particular case this doesn't matter much, because the site itself controls where to load the JSON from. Manipulating the JSON response requires access to the webserver or the DNS, and in those cases the attacker could have manipulated the initial HTML response as well.
> By eval()ing your json you are doing most of the attackers work for them. All that stuff in the article about mime types is redundant if you're eval()ing your json.
This totally misses the context of the article. The article is about CSRF. That is, the _attacker_ downloads and executes the JSON.
This is _not_ about downloading the attacker's JSON! It is about how to construct the JSON in a way that it is unaccessible through a <script> tag from the attacker's site.
https://secure.wikimedia.org/wikipedia/en/wiki/Same_origin_p...
This attack mentioned in the OP is effectively completely different. It is off a GET request.
Imagine you were running a social network site, and you had an API (authenticated via HTTP sesions) that was a GET request go get the firends list. This method returned the (logged-in) users list of friends in a JS array.
Note that Django-style CSRF tokens are not relevant here, as they are only for protecting POSTs.
The attack described in the post is using a script tag, and a redefined array setter, to direct a user with a live logged-in session on your site to it to fetch data.
So coming back to my example. I am a malicious hacker, and I can socially engineer an end-user to come to my site. I put a script src=yoursocialnetwork.com/get_friend_list. This will fetch the data, and I will be able to extract that info in my javascript, and then post that back to my site so I can capture that info.
> Is there a reason not doing so, like savings in bandwidth or something
> like that?
GETs may perform slightly better, see http://developer.yahoo.com/performance/rules.html#ajax_getApologies if the title made it seem like not using javascript arrays was a magic bullet to preventing all CSRF.
Regardless, it seems CSRF is much more widely known than XSSI, so you could say worrying about the distinction is just pedantry. I was very surprised when I searched earlier and could not so much as find an OWASP mention of XSSI. Still very important to know though.
It obviously worked this way at some point (http://news.ycombinator.com/item?id=2668888), so I'm guessing the older IEs, at least, still have this flaw.
In Firefox 4 and other new browsers this is no longer the case. But right now it's still an issue as many people are using older browsers.
* It needs an array literal (eg. []) to be constructed using the function referenced by the Array property on the global object. (This was spec ambiguity and only effects older IE and Firefox -- very old firefox, maybe only up to netscape or phoenix?)
* It needs assignments in object and array literals to call setters on the prototype chain.
Both of these issues were fixed by ES5 (the first may have been fixed in ES3.1) by saying that Array and Object literal notation both use the initial values of Array and Object (so you can't change the constructor used), and by saying that all assignments are "direct" so won't call setters on the prototype chain.This effectively makes JSON hijacking impossible, except of course for the large numbers of old browsers that are out there.
This is also a distinct issue from JSONP hijacking, for which there isn't a solution other than to not use JSONP.
It seems the best solution is not to use a top level object but (as mentioned below) Facebook's solution to prepend for(;;) to all JSON and strip it before parsing or Google's to prepend 'throw' and strip it pre-parsing.
Update: This conversation says it's not possible, but I'm still not a believer: http://sla.ckers.org/forum/read.php?2,35337,35337
The HTTP referrer, or HTTP "referer" as it is now permanently misspelled, should always come from your own domain. You could reject any form posts from alien referrers. However, this is risky, as some corporate proxies strip the referrer from all HTTP requests as an anonymization feature. You would end up potentially blocking legitimate users. Furthermore, spoofing the referrer value is extremely easy. All in all, a waste of time. Don't even bother with referrer checks.
curl --referer http://www.example.come http://www.example.com
Atwood's point is that checking the "referer" will both be unreliable and, more importantly, lead to false positives; there are better alternatives, namely, double submitting cookies as I have pointed out elsewhere with regard to this article.
I agree, this is not the attack I think of when somebody mentions CSRF. Well, the solution at least isn't. I would be very suspicious of anyone who claimed to solve their CSRF holes by not using arrays.
Here are a couple of resources that go a litle more into XSSI:
Google tech talk: http://www.youtube.com/watch?v=jC6Q1uCnbMo&feature=playe...
Gruyere codelab: http://google-gruyere.appspot.com/part3#3__cross_site_script...
https://github.com/documentcloud/backbone/issues/201
ultimately you should use tokens to verify the request was not forged.
a) Resources using something other than GET are automatically not affected.
b) For GET resources, require that the body of the request contains a value, perhaps from a cookie but could be anything, ensuring that the request was made using xhr, which is domain restricted.
Sounds like the simplest way to me, curious why it wouldn't work.
For point B, I notice that I get an "x-requested-with: XMLHttpRequest" when doing ajax() from inside jQuery. I assume this is not there when someone SCRIPT SRCs something (why would it be?), so that may be useful to someone.
> So now what happens if a clever hacker is embedding this to his website and social engineers a victim to visiting his site. >
If that happens then CSRFs or JSON is not the highest priority thing to worry about. The hacker controls everything. And no matter what I do, he can find a by pass.