
JSON users: Avoid CSRFs by not using top-level arrays - Jach
http://flask.pocoo.org/docs/security/#json-security
======
adolph
I'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-v...](http://freedom-to-tinker.com/blog/wzeller/popular-websites-
vulnerable-cross-site-request-forgery-attacks)

Via: [http://www.codinghorror.com/blog/2008/10/preventing-csrf-
and...](http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-
attacks.html)

~~~
alanning
Haacked has a step-by-step description of how this attack works:
[http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-
js...](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-
vulnerability.aspx)

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

~~~
mustpax
That wouldn't work, you can override the Object constructor in JavaScript as
well. You have to prepend something like `while(1);` to the JSON responses
you're returning.

~~~
alanning
I can see why you would think that but it does work because a script that just
contains a JSON object is not a valid JavaScript file. The browser will give
an error. Whereas a script that just contains a JSON array is valid.

------
jcampbell1
This attack was used successfully against gmail in Jan 2006.

[http://jeremiahgrossman.blogspot.com/2006/01/advanced-web-
at...](http://jeremiahgrossman.blogspot.com/2006/01/advanced-web-attack-
techniques-using.html)

------
bemmu
Facebook is dealing with this by prefixing some of their JSON responses with
"for(;;);".

~~~
jarin
Sometimes the laziest solutions are the most elegant.

~~~
telemachos
Tangential: 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>

~~~
AndyNemmity
Interesting 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.

------
the_mitsuhiko
As 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.

------
mumrah
Generally 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/>

~~~
ZoFreX
Having a CSRF token on your forms does not prevent, and has nothing to do
with, with the attack in the article.

~~~
mmmm
Hi, 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!

~~~
adamt
The 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.

~~~
mmmm
Thanks a lot for the excellent explanation. I tend to do my Ajax requests with
post just to get the token in. Is there a reason not doing so, like savings in
bandwidth or something like that? Might that be a gain Facebook is trying to
achieve?

~~~
rimantas

      > 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_get>

------
fmavituna
Title is incorrect, Array usage is about Javascript/JSON Hijacking not
protection against CSRF.

~~~
personalcompute
Correct, the technical term for it is 'XSSI', or Cross Site Script Inclusion.

~~~
Jach
I 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
They 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.

------
kkaefer
Array() doesn't seem to be called when defining an array with [] in Chrome 12
and Firefox 3.6+

~~~
locci
Because [] 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."

~~~
the_mitsuhiko
> 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
I just tested with Firefox portable 3.0.0 and it looks like it was already
fixed then (June 2008).

------
olliej
The 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.

------
mmaunder
Does 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>

------
zacharyvoase
An 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
From [http://www.codinghorror.com/blog/2008/09/cross-site-
request-...](http://www.codinghorror.com/blog/2008/09/cross-site-request-
forgeries-and-you.html)

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
How 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
You cannot assume a request is coming from a browser.

curl --referer <http://www.example.come> <http://www.example.com>

~~~
personalcompute
Yes, 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).

~~~
sonnym
I was merely pointing out how easy it is to fake, although you are correct a
third party site could not do this.

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.

~~~
personalcompute
He's definitely correct about the false positives and that nonces and/or
double submitted cookies are superior, I'm not arguing that, just the
'furthermore, spoofing is extremely easy,' which makes no sense there.

------
personalcompute
Isn't this XSSI, not CSRF?

~~~
tedunangst
Apache 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
Cross 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
Yes, 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=playe...](http://www.youtube.com/watch?v=jC6Q1uCnbMo&feature=player_detailpage#t=2306s)

Gruyere codelab: [http://google-
gruyere.appspot.com/part3#3__cross_site_script...](http://google-
gruyere.appspot.com/part3#3__cross_site_script_inclusion)

------
tosh
further 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>

ultimately you should use tokens to verify the request was not forged.

------
thadeus_venture
Why 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.

~~~
rachelbythebay
Point 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.

------
z92
Here 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.

------
simonista
Does anyone know how to rectify this if you're using the Rails 3 respond_with
pattern?

------
wanderr
This is a non-issue if you use JSON-RPC.

~~~
wanderr
What's with the downvotes? Is my statement not true??

