Proposal for safe jsonp

2010-11-09 22:49:15

One of the big thorns in the eye of any 2.0 developer is the security model surrounding "JSONP". In order to get the content (the JSON) you need for a certain "mashup", you're required to include a "hostile" script into your page. Essentially executing an XSS. And while most content providers can be considered pretty much safe, some simply aren't. The advertisement sector is especially notorious for this problem.

JSONP rests on the basics of (virtually) unrestricted script inclusion. JSONP is basically a JSON object, wrapped in a function call (a callback). The P stands for padding. It is not limited by cross domain restrictions. It's not limited to its own sandbox. Everything goes in the same global scope. That has to end.

There is a solution to this problem. It is called CORS, Cross-Origin Resource/Request Sharing. If you're looking for a way to do proper XHR across domains, CORS is the way to go.

Of course, CORS entails some server side setup. It adds a layer of complexity to your application. For some people it's simply not worth it. I've incidentally spent (more like wasted) half a day today setting it up because browser feedback is still rather limited. You're working in the dark. And while that should not be an argument against using CORS, for a lot of people it will not be worth the effort. Not while JSONP is possible.

And JSONP remains possible as long as scripts have this cross domain freedom. And that may never change because we can't break the web. Furthermore, JSONP isn't exactly a "technology" that was used in the old days. No, it's being actively used today. It's actively setup today. To get rid of it is like getting rid of IE6. How old is that again?

So having established that JSONP is going to be around for a long time there needs to be a solution. CORS is not going to be the ultimate solution, although I still encourage using it if you can.

So what are the alternatives? You can use an iframe to sandbox the script and use cross iframe messaging to.. oh wait, you said cross domain? Yeah never mind, forget it.

There are no (true) alternatives. Maybe one could be found in third party environments, but if flash or silverlight could do it, wouldn't we already be praising them for finally doing something right?

JSON itself, by the way, is not quite unsafe. The general consensus is that JSON, fed through JSON.parse, is acceptably safe. It's a bunch of strings and numbers in array and object literals.

So here's my proposal. I've spoken with a few people about it. It's not going to be easy, but I seem to be on a tiny crusade for this one.

I propose a jsonp.load(url, onLoad, onError) (name is up in the air, I don't care about naming the) function. When invoked, the function will place a request with the same access restrictions as a script tag and always without cookies. Optionally, cookies could be allowed if they are allowed by CORS headers.

Now the document fetched with .load should contain JSONP. In fact, if it doesn't contain JSON wrapped in a callback call, it should be rejected immediately (firing the onError). The accepted grammar would look like this:

Code: (CFG)
JSONPText :: Identifier ( JSONText ) ;

The semi-colon is optional. JSONText and Identifier are evaluated as per the ECMAScript 5 specification.

Upon evaluation, the identifier, although required, would be stripped and ignored. It serves to protect bare JSON from being stolen. You can currently not retrieve pure JSON data cross domain. If we didn't add this precaution, you would be able to "steal" any JSON object from the web and we don't want to allow that. So the JSON object must be wrapped in a function call. This data is currently up for grabs through remote script inclusion anyways, so I feel it's safe to say that it has a reasonable expectation to be public.

The .load function would take the JSONText, parse it through JSON.parse (already available in the browser) and return the JSON object to the onLoad callback.

Ta-da. That's it.

Example usage:

Code: (js)
jsonp.load(
'http://example.domain/my/rest/interface.js',
function(json){ console.log("Received json", json); },
function(message){ console.log("Error retrieving json", message); }
);

I really think this is an elegant method of doing safe JSONP and getting rid of the long standing security risk of including JSONP through script inclusion, without the additional "setup and maintenance" cost of CORS.

So let me recap. My method would:

- have the same security model as script tags
- not send cookies (optionally: unless the CORS model allows it)
- not execute the callback to prevent the security risk
- but require the callback wrap to prevent stealing
- not allow downloading arbitrary JSON objects
- use the existing JSON.parse to parse the actual JSON
- return the JSON to a callback, preserving application logic
- not require globals to do JSONP
- no longer require fire and pray to do JSONP
- not require any additional setup to JSONP
- still be JSONP, in the sense that P stands for padding
- does not introduce any new abusable attack vectors
- let you stay in control
- be safer.

All rejections so far seem to be in the "but we have CORS" or that JSONP is unsafe by design. I only want to make an existing and popular methodology safer to use. Help me help the web.

PS. Yes, I know about Douglas' JSONRequest. It's not the same. His proposal regarded regular JSON, concerned receiving and sending, is more bloated (or worked out better... ;)) and has a different approach angle.