

A Question about CSRF JSON hijacking - tosh
https://github.com/documentcloud/backbone/issues#issue/201

======
nbpoole
I just posted this on the issue:

\----

I would be wary of solving the problem this way.

The problem is the object/array combo you're talking about is still perfectly
valid JavaScript. However, due to the way browsers handle JavaScript, it's not
currently possible to access the data from it. If you do implement it, you run
the risk of a browser changing its behavior ever so slightly and destroying
your security. ;-)

The way I've typically seen this done is to add a prefix of some kind to the
data. The prefix can be valid JavaScript (eg: while(1); or throw 1;) or it can
be text that JavaScript can't parse. Either way, the point is to ensure that a
cross-domain request via the <script> tag will fail and not be able to get to
the data. These solutions are slightly better because it's less likely that
these constructs will become valid in the future (although still possible).

With that being said, here's the advice from the Browser Security Handbook,
which advocates for a slightly different solution
(<http://code.google.com/p/browsersec/wiki/Part2>):

 _Note: quite a few JSON interfaces not intended for cross-domain consumption
rely on a somewhat fragile defense: the assumption that certain very specific
object serializations ({ param: "value"}) or meaningless prefixes (
&&&START&&&) will not parse via <SCRIPT SRC="..."> or that endless loop
prefixes such as while (1) will prevent interception of the remainder of the
data. In most cases, these assumptions are not likely to be future-safe; a
better option is to require custom XMLHttpRequest headers, or employ a parser-
breaking prefix that is unlikely to ever work as long as the basic structure
of JavaScript is maintained. One such example is the string of )]}', followed
by a newline._

~~~
jashkenas
Thanks for commenting ... When we discussed it in #documentcloud, that was the
consensus we came to as well -- it's a bit silly to slightly tweak your JSON
in this fashion, when browsers are still opening holes to make it executable
in other ways:

[http://directwebremoting.org/blog/joe/2007/03/06/json_is_not...](http://directwebremoting.org/blog/joe/2007/03/06/json_is_not_as_safe_as_people_think_it_is_part_2.html)

Doing a `while(1);` would be much more robust.

~~~
coderrr
It seems to me the suggestion to require the 'X-Requested-With:XMLHttpRequest'
header to be set is the best way to handle this, much better than some JS
tweaking. It doesn't make any assumptions about javascript syntax or
implementations. And the only way I can think to get around it is some http
request splitting exploit via a proxy.

If you only return data to XHRs then you're protected by same origin policy
and all <script> tags will get no data.

-EDIT-

When I first read "a better option is to require custom XMLHttpRequest
headers" I thought this is what they were talking about. After a second look
they probly mean setting a custom header yourself using the XHR object. This
would work too but now I'm wondering if there's some way around my solution.
Because why would they advocate a custom header if checking the X-Request-With
header alone is enough?

\- EDIT -

Was just in the shower and remembered why they probly don't advise to just
check the X-Requested-With header. There are ways for an attacker to get
around the XHR same origin policy with dns pinning/rebinding attacks. If you
required a custom header with a session cookie which the attacker didn't have
access to this would mitigate that kind of attack. EDIT: Nevermind, as long as
you're checking the Host header a dns rebinding attack wouldn't matter.

~~~
jashkenas
This sounds like an even better fix -- nicely done.

~~~
mrkurt
You could just use the Origin header from CORS:
<http://www.w3.org/TR/cors/#origin-request-header>

~~~
coderrr
But how many browsers actually send the Origin header currently? I just
checked Firefox and Chrome and neither sent it on a <script> or XHR request.

~~~
nbpoole
It definitely isn't set via <script>. I know it's sent when you make an XHR
request via jQuery, so I assume you can set it as a custom header if you're
rolling your own XHR.

~~~
coderrr
Ah sorry, I was only checking same domain XHRs, which doesn't seem to send the
Origin header. That combined with the fact that Origin isn't sent for <script>
tags seems like it can't be used to prevent CSRFs.

~~~
nbpoole
It might not send it automatically (I don't know, I haven't tested it), but
since you're the one building the XHR, you can send the header if you want.
The same origin policy won't let you see the results of a cross-domain
request, so it seems like requiring the header is an effective technique.

Obviously this is only true for read operations: anything that updates state
would still be vulnerable to CSRF (since you don't need to be able to read the
result to make the request).

~~~
coderrr
I'm pretty sure you can't set the Origin header yourself. That would kindof
defeat the purpose of it. If anything they'd only allow you to specify whether
or not to include the header. But I doubt any browser even lets you do that:
$.ajax({url:'<http://>
asdf.com/1,beforeSend:function(xhr,set){xhr.setRequestHeader('Origin',
'<http://> realorigin.com)}}) Refused to set unsafe header "Origin"

So since you can't force an Origin header to be on all of your legit API
requests, you won't be able to differentiate them (using the Origin header)
from an attacker with a <script> tag.

~~~
nbpoole
I'm not seeing the same behavior you're seeing. Try using $.get instead of
$.ajax?

Edit: I was confusing X-Requested-With and Origin. My apologies.

