14 ms·
JSON users: Avoid CSRFs by not using top-level arrays
- bemmu 15y agoFacebook is dealing with this by prefixing some of their JSON responses with "for(;;);".
- jarin 15y agoSometimes the laziest solutions are the most elegant.
- telemachos 15y agoTangential: One of my favorite pieces about simplicity, laziness, dogmatism and getting things done is from Mark Jason Dominus[1]. His context isn't connected to this at all (it's about when and whether to use shell commands inside Perl scripts), but the larger point is very relevant: taking "Do the simplest thing that could possibly work" seriously can have surprising outcomes. tl;dr Sometimes ugly is elegant, too. [1] http://perl.plover.com/yak/12views/samples/notes.html#sl-3 http://perl.plover.com/yak/12views/samples/notes.html#sl-3
- AndyNemmity 15y agoInteresting article, and I had a similar issue with #perl recently as well. 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.
- gambler 15y agoThey are adding executable code at the beginning of what is supposed to be a data structure. This solution is not elegant at all. It's an ugly hack that uses a side-effect of code to plug a hole in a rather convoluted security model. 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.
- timmorgan 15y agoOK, I see how that prevents an attack (it seems to create an endless loop), but how does this parse as regular JSON for the good guys?
- sonnym 15y agoI imagine they simply remove the first 8 characters from the response string. eval("(for(;;);{})"); doesn't work, but eval("(" + "for(;;);{}".substring(8, 10) + ")"); works fine where 10 is the length of the response string. 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.
- JonnieCache 15y agoYou should never be eval()ing json, thats the most dangerous thing you can do with it. All browsers have had native JSON parsers for years, which will choke on any code that's sent through them. These will be automatically employed by any js framework you're using, or I think its generally JSON.parse(). 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.
- sonnym 15y agoWhile I generally agree with your sentiment, I was merely writing a brief proof of concept test in the firebug console. From: http://www.json.org/js.html 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.
- deleted 15y ago[deleted]
- benjoffe 15y agoYes and Google has 'throw 1;' at the start of some of their files IIRC (may be some other value than '1').
- adolph 15y agoI'm not familiar with CSRF so had to look this up: [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-vulnerable-cross-site-request-forgery-attacks http://freedom-to-tinker.com/blog/wzeller/popular-websites-v... Via: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html http://www.codinghorror.com/blog/2008/10/preventing-csrf-and...
- cheez 15y agoI hope you're not a web programmer. I'm not a web programmer and I knew that.
- yoyar 15y agoAnd the award for most pompous, unnecessary, and conceited response goes to... cheez!
- espeed 15y agoA few days ago I asked, "Ask HN: Best Approaches to Prevent Session Hijacking?" (http://news.ycombinator.com/item?id=2663293 http://news.ycombinator.com/item?id=2663293) and presented a technique I'm experimenting with that uses session timestamps as tokens to validate that each request is authentic and an attacker hasn't stolen the session key. 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?
- 15y ago
- tosh 15y agofurther reading: 'Would it be more secure if API endpoints for Collections would wrap the JSON serialized Arrays inside an Object literal?' https://github.com/documentcloud/backbone/issues/201 https://github.com/documentcloud/backbone/issues/201 ultimately you should use tokens to verify the request was not forged.
- mumrah 15y agoGenerally speaking, all incoming requests should be verified - authorized or not. These days with all kinds of wonderful web frameworks, CSRF protection is pretty simple. Django handles CSRF with a token in a hidden form field: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/ https://docs.djangoproject.com/en/dev/ref/contrib/csrf/
- DasIch 15y agoThis does not help at all in this case, proving that it is not that simple.
- ZoFreX 15y agoHaving a CSRF token on your forms does not prevent, and has nothing to do with, with the attack in the article.
- mmmm 15y agoHi, why not? If the attacker can't get any information without sending a token with the request, what's there to worry about? I'm not very good with js so I might have misunderstood something. Thanks!
- Johngibb 15y agoI believe that he's right that just putting it in a hidden form field would be useless for this sort of attack. However, I believe rails and django actually include the CSRF token in headers for ALL ajax requests, not just form submits.
- adamt 15y agoThe Django "{% csrftoken %}" that you put into forms (and similar things in other frameworks), is used when posting form responses. It turns into a hidden form field (<input type=hidden>). This helps protects against someone creating a form on their own malicious site, that posts some data to yours. 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.
- personalcompute 15y agoIsn't this XSSI, not CSRF?
- tedunangst 15y agoApache Extended Server Side Includes? Haven't seen XSSI before. What's the I? 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.
- personalcompute 15y agoCross Site Script Inclusion. The article touches on a lot of things (including CSRF), but the HN title refers specifically to JSON (As well as the link's '#' fragment identifier), and therefore the last section, where it shows an attack site including unprotected JSON through the <script> tag, and unless I'm seriously mistaken, this is the definition of XSSI.
- pmh 15y agoYes, this is XSSI and the exploit comes in because a JSON array is essentially treated as "executable" code in some JS implementations. Here are a couple of resources that go a litle more into XSSI: Google tech talk: http://www.youtube.com/watch?v=jC6Q1uCnbMo&feature=player_detailpage#t=2306s http://www.youtube.com/watch?v=jC6Q1uCnbMo&feature=playe... Gruyere codelab: http://google-gruyere.appspot.com/part3#3__cross_site_script_inclusion http://google-gruyere.appspot.com/part3#3__cross_site_script...
- jcampbell1 15y agoThis attack was used successfully against gmail in Jan 2006. http://jeremiahgrossman.blogspot.com/2006/01/advanced-web-attack-techniques-using.html http://jeremiahgrossman.blogspot.com/2006/01/advanced-web-at...
- kkaefer 15y agoArray() doesn't seem to be called when defining an array with [] in Chrome 12 and Firefox 3.6+
- locci 15y agoBecause [] is not syntactic sugar for new Array. The code the article wrote about doesn't work in any implementation that I know of. I think the ESv3 spec wasn't very clear: "Create a new array as if by the expression new Array()." But the implementations (always?) did the right thing, and the ESv5 spec is more clear: it adds "where Array is the standard built-in constructor with that name."
- gruseom 15y agoThis comment and the GP suggest that the article is wrong. Is it? Can anyone confirm a JS implementation that manifests the described behavior (the Array constructor being called when parsing top-level [] in JSON)? It obviously worked this way at some point (http://news.ycombinator.com/item?id=2668888 http://news.ycombinator.com/item?id=2668888), so I'm guessing the older IEs, at least, still have this flaw.
- ivank 15y agoThe attack worked in an older Firefox, at least.
- the_mitsuhiko 15y ago> Because [] is not syntactic sugar for new Array 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.
- locci 15y agoI just tested with Firefox portable 3.0.0 and it looks like it was already fixed then (June 2008).
- simonista 15y agoDoes anyone know how to rectify this if you're using the Rails 3 respond_with pattern?
- z92 15y agoHere is the catch: > 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.
- mmaunder 15y agoDoes anyone know if it's possible to override constructors for the global "Object" in Javascript? The author's assumption is that it is not possible and therefore the best solution to this is to wrap all your JSON with {} instead of []. My intuition is telling me that's not the fix. Can someone verify? 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 http://sla.ckers.org/forum/read.php?2,35337,35337
- deleted 15y ago[deleted]
- fmavituna 15y agoTitle is incorrect, Array usage is about Javascript/JSON Hijacking not protection against CSRF.
- aidenn0 15y agoYeah, the article got it right (CSRF was the previous section) but the submitter titled it wrong.
- personalcompute 15y agoCorrect, the technical term for it is 'XSSI', or Cross Site Script Inclusion.
- Jach 15y agoI haven't heard of that term before now, but I would argue that XSSI is a way of doing CSRF rather than CSRF, or something entirely different itself. (From Wikipedia: "Unlike cross-site scripting (XSS), which exploits the trust a user has for a particular site, CSRF exploits the trust that a site has in a user's browser.") Apologies if the title made it seem like not using javascript arrays was a magic bullet to preventing all CSRF.
- personalcompute 15y agoThey are definitely similar, but I wouldn't say XSSI is a type of CSRF. CSRF refers specifically to a few methods of attack, distinct from what is used in XSSI. 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.
- fmavituna 15y agoBased on what? Never heard of XSSI in this context, isn't XSSI used for extended server side includes?
- zacharyvoase 15y agoAn idea: when using session-based authentication and returning JSON, check that the Referer [sic] matches your domain. I think I'm missing something major, though.
- sonnym 15y agoFrom http://www.codinghorror.com/blog/2008/09/cross-site-request-forgeries-and-you.html http://www.codinghorror.com/blog/2008/09/cross-site-request-... 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.
- personalcompute 15y agoHow is spoofing the referer extremely easy? I can think of no way for an attack site to do this, short of having control over the entire user machine (or a significant browser exploit anyways), at which point all of your webapp security is irrelevant.
- sonnym 15y agoYou cannot assume a request is coming from a browser. curl --referer http://www.example.come http://www.example.come http://www.example.com http://www.example.com
- personalcompute 15y agoYes, but if the user wishes to avoid their own security like this, they can already do it a thousand other ways (and to no adverse affect, there is no opportunity for an attacker to exploit this, I don't know what you're getting at).
- the_mitsuhiko 15y agoAs the author of said document: The title here is misleading. While this attack is a form of CSRF, doing that does not magically solve all your CSRF problems. It just counters one particular attack vector which are JSON responses.
- thadeus_venture 15y agoWhy would this not work: 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.
- ww520 15y agoGood thought. The "double-submitted cookie" technique does exactly that.
- rachelbythebay 15y agoPoint A sounds perfect to me. Why bother with GET at all? 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.
- wanderr 15y agoThis is a non-issue if you use JSON-RPC.
- wanderr 15y agoWhat's with the downvotes? Is my statement not true??
- olliej 15y agoThe articles information is very out of date, and relies on multiple issues to successfully work: * 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.