There have been lots of explanations recently of the dangers of JSON or JavaScript remoting. This post is about what you can do to protect your scripts.

The Problem

The issues have been explained before, so I'm going to assume some knowledge of the problem. If you're not sure, some stuff to read:

The Solutions

So what are the solutions? I think there are 3 options. Each has their pros and cons:

Use a Secret in the Request Force pre-eval() Processing Force POST requests

This post is a shortened version of the detailed description of what DWR does If you are interested in the options DWR takes and how you can configure it, you should read page.

1. Use a Secret in the Request

If you can only support one of these protections, this is the one to chose. Including a secret in the request allows the server to reject the request as invalid before any actions take place. It is common to include the secret in the URL, however this is a slightly vulnerable position for a secret since it is likely to turn up in web server log files and so on.

It is possible to use cookie values like PHPSESSIONID or JSESSIONID read using Javascript as the secret. The browsers cross-domain rules should prevent attackers from discovering this cookie. However this method will stop working as people begin to switch to using HttpOnly cookies. So it is better to start with a separate secret.

2. Force pre-eval() Processing

Since <script> tag remoting does not allow you to process the JSON or Javascript before it is eval()ed you can protect your JSON by forcing it to be manipulated before eval(). All 3 of these techniques will prevent your request from being pure JSON, however you may rank security above purity. There are 3 ways to do this:

Wrap the JSON in a comment. For example, /* { 'data':'protected' } */ . When this is eval()ed, there will be no result, however if you have fetched the data using XHR or iframe, you can do some string manipulation before eval() to remove the leading /* and trailing */.

This method is good for plain JSON, however if you are using Javascript which could contain /* comments */, then you should not use this technique because comments in Javascript do not nest.

. When this is eval()ed, there will be no result, however if you have fetched the data using XHR or iframe, you can do some string manipulation before eval() to remove the leading /* and trailing */. This method is good for plain JSON, however if you are using Javascript which could contain /* comments */, then you should not use this technique because comments in Javascript do not nest. Prefix the script with ' while(1); ' Since this is an infinite loop, if causes browsers to hang, and maybe give an error message. Either way the script does not get executed.

There is a potential vulnerability that some browser may allow you to override the action of while using something like this: ' function while() {} '. However I don't know of any such browser.

Google use this method to protect data in GMail. ' while(1); ' is possibly better than ' while(true); ' in case there are any browsers that allow you to redefine truth.

' Since this is an infinite loop, if causes browsers to hang, and maybe give an error message. Either way the script does not get executed. There is a potential vulnerability that some browser may allow you to override the action of while using something like this: ' '. However I don't know of any such browser. Google use this method to protect data in GMail. ' ' is possibly better than ' ' in case there are any browsers that allow you to redefine truth. Prefix the script with ' throw new Error("message"); '. This is a neat solution in that it allows you to explain what is wrong to users that get the message by mistake. DWR uses this method.

It is potentially vulnerable to some browser allowing an attacker to redefine the Error or String constructors to prevent the throw from happening, however this does not work on any browser that I know of, and it's hard to see how it could happen.

3. Force POST Requests

Since browsers use GET to process <script> tags, you can prevent <script> tags from working by denying GET requests for some JavaScript resource. This is the most common solution, however it is also perhaps the weakest.

Firstly XHR-POST doesn't work with older versions of Safari, so some support for GET is often useful.

More importantly future versions of Firefox are touted to include cross-domain XHR support. While we don't have exact knowledge of how this will happen, it would be foolish to base your security plans on this technique holding up.

Finally, we're working in an environment where new possibilities are popping up every day - betting your security on a system that works more by fluke than design isn't a great idea in my opinion.

By default DWR denies POST requests for belt and braces security, however this is customizable to allow support for older versions of Safari.