JSON is not as safe as people think it is
I saw some discussion recently about using JSON for secured data, and I'm not sure that everyone understands the risks.
I believe that JSON is unsafe for anything but public data unless you are using unpredictable URLs.
There are 2 problems. CSRF (Cross Site Request Fogery) allows attackers to bypass cookie based authentication. I blogged about it a while ago. Wikipedia talks about it. CSRF allows you to invoke cookie protected actions on a remote server. It allows Mr. Evil to trick Mrs. Innocent into transferring money from her bank account into his.
Far less known perhaps, is the JSON/Array hack that allows a user to steal JSON data on Mozilla and any other platform with a modern JavaScript interpreter.
There are many ways to fetch data from a server, but the interesting cases here are: XHR, iframe and script-tags. Without knowledge of the JSON/Array hack it's easy to reason like this:
- XHR: Browser cross-domain rules prevent the attacker from making the request in the first place.
- iframe: The attacker can embed an iframe that points at some remote server (the bank in the above example) and ask to be sent some JSON, but browser cross-domain rules prevent scripts from the attackers domain from reading the reply, so the JSON is safe because it will never be
eval()ed
. - Script-Tags: The attacker can embed a script tag pointing at a remote server and the browser will effectively
eval()
the reply for you, however it throws away the response and since JSON is all response, you're safe.
It's the last of these arguments that is suspect. The dynamic nature of JavaScript will let you redefine how the browser evaluates the JSON.
Here's how it works, and you can follow along with any JavaScript console:
- Redefine the Array constructor:
function Array() { alert("hi"); }
- Verify that this constructor is called when arrays are created:
var a = [ 43 ];
- Use the new feature to manipulate the array:
function Array() { this[1] = 50; } var a = [40]; alert(a[0] + a[1]); // Gives 90
So we can call secure JSON data using CSRF with a script tag to by-pass the cookie authentication, and then use the JSON/Array hack to steal the JavaScript data from the browser as it processes the script-tag.
So we've redefined the Array constructor, how do we actually get the data out? The syntax below works in current versions of Firefox, although from my reading of the spec proposals, it's not a part of Javascript 2, and it appears to fail in IE/Safari/Opera.
Create a web page at evil.com, with a couple of script tags like this:
<script type='text/javascript'> function Array() { var obj = this; var ind = 0; var getNext = function(x) { obj[ind++] setter = getNext; if (x) alert(Data stolen from array: " + x.toString()); }; this[ind++] setter = getNext; } </script> <script type='text/javascript' src='http://bank.com/jsonservice'> </script>
The long and short is that JSON is not safe in any system that uses cookies for authentication.
With DWR we use full JavaScript which is as vulnerable as JSON, however DWR's CSRF protection automatically uses the doubly-submitted cookie pattern to provide extra safety.
I'm by no means the first person to think of this; Jeremiah Grossman used it to break GMail over a year ago.
Update: If you are doing JSON 100% properly, then you will only have objects at the top level. Arrays, Strings, Numbers, etc will all be wrapped. A JSON object will then fail to eval() because the JavaScript interpreter will think it's looking at a block rather than an object. This goes a long way to protecting against these attacks, however it's still best to protect your secure data with un-predictable URLs.
Comments
In the script-tag based case the interpreter can tell the difference, (can't think why right now).
You can wrap the JSON in () to force the interpreter to think of it as JSON and not a code block.
e.g.
Those planning on writing future-proof exploit code should note that 'setter' (and 'getter' for that matter) is deprecated. See this link for more info.
Using the new syntax; the sample code looks like this:
My worry is that so few people understand the issues. Take a look at the comments, particularly on the Ajaxian thread, and you will see a lot of confusion, and the confused are creating insecure webapps.
Comments have been turned off on old posts