It's August, the sun is sinking on a beautiful sunny day in the heart of London and everyone who can has packed into the capital's beer-gardens.
One of the most popular pubs at this time of year is The Server Inn on the river Webb. Owned by a wealthy American named Bezos, it's become the spot to be seen since getting a license to serve drink on the adjacent riverbank.
Jeff was so proud of this new feature he trademarked it under the name Webb-Service and even drafted in some foxy waitresses to deliver it.
Webb-Service proved to be one of the smartest moves Bezos made with and without fail, summer nights see the river bank packed out with punters enjoying a pint in the evening warmth.
Regulations
Local government being local government, licensing Webb-Service was a tricky process.
During the final hearing, it was suggested that too healthy a combination of heat and Hoegaarden could easily lead to trouble.
Bezos was the first to agree that drunken punters toppling into the river and getting lairy in the Server wouldn't be in anyone's interests.
After a brief discussion, someone suggested that perhaps the barmen should keep an eye on whether drinks were going outside or staying in the pub. If they noticed any particular table ordering more than seemed sensible, they could always stop serving them and ask them to move on.
Bezos agreed that the idea made sense. Just as the license was about to be granted though, one of the other councilors piped up, "How will they know though? How will the barmen know whether a drink's for inside or outside the pub?".
"Easy", replied Bezos, "the server only does table service and our foxy, fiery waitress can just tell the people behind the bar which drinks are for Webb-Service and which are local".
An unexpected twist
"No, that's not going to be good enough" replied the council. "We're going to need a formal procedure for this Webb-Service. Hows about if outside orders were only allowed to be taken in German?"
Bezos: "Sorry?"
Councilor: "Take Webb-Service orders in German and internal orders in English, that way you'll know for sure where they came from. Only hand drinks over to users, ahem, customers, ordering in German."
Bezos: "You're kidding right? We could just have the waitresses tell the servers that an order is for Webb-Service?"
Councilor: "What if they forget to say so?"
Bezos: "What if they don't bother speaking German?!"
Councilor: "I'm afraid you're over-ruled Mr Bezos. The stipulation will be that all drinks ordered through your Webb-service must be ordered in German. The waitresses must fetch all orders, deliver them to the customer but only hand them over if the customer can repeat the original order. In German."
Bezos: "This is insane. I'm going to have to get all my customers, my foxy waitresses and my serving boys to learn German just for the sake of delivering Webb-Service? What happens if they deliver the drinks and the customers can't speak German?"
Councilor: "In that case Mr Bezos, you'll simply have to throw the drinks away. Orders from within the bar can be made in English but all orders from the river must come in German. Case closed."
Bezos: "Good grief. You're all insane."
Concilor: "We're not insane, Mr Bezos. We're government."
German (JSON)
We coders laugh in the face of such fumbling bureaucracy.
Clearly the onus for informing the servers lies with the waitresses. Whether they make the order in German is neither here nor there, all that matters is that they tell the server whether it was a Webb-Service order or internal.
The proposed system doesn't actually even inform the servers at all, it requests drinks whether or not someone's on the river bank and simply tips them away if they're not authorised to receive them.
We developers would clearly never implement such a foolish system... would we?
XMLHttpRequest and <script />
Unfortunately, it seems we would and we have. Welcome to the world of XMLHttpRequest (XHR).
As anyone who's worked with XHR knows, it can only be used to fetch data from the host site. All external requests are denied by the browser.
We can still get data from other servers but only by using a <script /> tag to pull in raw JSON.
The dangers of JSON
Where XML is commonplace with powerful tools implemented in most programming languages, JSON support is sparse at best. Not only that but JSON is far more than just data.
JSON is pure code executed in the scope of the script that receives it. This might be acceptable for trusted same-domain JSON but is extremely dangerous when we can't be certain of the objectives of the script author. By executing their script, we give them full access to our users' web-pages and data.
Cross-domain scripts could prove to become one of the biggest security holes of Web 2.0.
Google and Yahoo might currently represent trusted companies but every time you place a Google Map, a Flickr slideshow or Google Analytics into your webpage, you give those companies full access to every piece of data within that document.
That data includes but is not limited to cookies, DOM code and any passwords or information people enter into the page's forms.
The current state of play
- We can request scripts data or images from any domain (XHR or iframe)
- Data from our own domain can be delivered in either raw executable JSON or the inert XML format
- Data from other domains can only be read if it's delivered in JSON
Why arbitrary cross-domain XHR is bad
Just as in Bezos' beer garden, it's important that the servers know whether a request is coming from. If I was able to send and read requests from arbitrary servers then I could easily use visitors to my websites for a brute-force attack on another site's passwords.
I put a little piece of code in a webpage that sends ten password attempts to Paypal and returns the results to my server. A few hundred thousand visitors later and I'll have myself a useable password or two.
I could of course level the same attack from my own server but thousands of password attempts coming from a single IP address are easy to spot and block. Thousands or even millions coming from different IP addresses are far harder to distinguish from legitimate requests.
Arbitrary cross-domain XHR makes any and every visitor to a website a potential zombie machine and is very definitely a bad thing.
What's the alternative?
So the problem is that we would like to be able to request data cross-domain but we need to make it clear to the server that this is the case.
Why not force the server to acknowledge that a request can be used cross domain? Flash uses a domain policy document on the server to achieve this but why not simply use the HTTP headers?
The entire cross-domain restriction is policed not by servers but by user-agents - browsers. Why not enable cross-domain XHR in browsers but only as long as the server actively agrees.
Authorising cross-domain requests using headers
If the server is happy to return cross-domain requests then it prepends them with:
Cross-domain-access: true
The browser only makes the data available to the XHR call if this authorisation header is present.
In this way, the system remains server opt-in but without the cumbersome requirement that opting-in requires all transactions to be processed in JSON.
Policing the system
My first reaction on reading the above proposal would be to object that it's client-policed and can therefore be over-ridden but then so is the current solution.
Cross domain XHR is limited only by the browser, not by the server and even then it's allowed when we use the script tag workaround.
Conclusion
Clientside cross-domain data requests are an extremely useful tool. They can only currently be done (in Javascript) using the script-tag workaround to deliver data as JSON.
Any cross domain XHR must be server opt-in (rather than opt-out) or else we leave all non-enabled servers vulnerable to brute-force attacks. Such an opt-in arrangement can very simply be communicated by having the server send a header authorising cross-domain use of the data.
External JSON is potentially very dangerous as it is arbitrary third-party code executed in the scope of the current web-page. It can be used to steal passwords or data present in the current scope. Not only that but JSON is not easily human-readable and is only an emerging data standard requiring server-side libraries that still aren't present or mature in most languages.
XML is already a data standard in wide use with API's present in most programming languages. It's inert, human-readable and highly compressible using gzip. It doesn't have the same execution convenience as JSON in a ECMAScript environment but doesn't need to be executed in an ECMAScript environment.
I don't know whether I'm missing a trick here but since the last 10 years have seen the development of a powerful language with a huge number of tools, meant specifically for packaging and accessing data, does it really make sense to bear off on a tangent with an entirely new format?
Any thoughts?


16 comments:
If it's not though, some sort of static solution needs to operate and sending allowed/denied domains in the header makes a lot of sense.
The header seems a more flexible fit to me than an XML file. I've never worked with the Flash implementation so can't comment with any experience. What could be the drawbacks to headers?
JSON is not the security problem here, dynamic script injection is the problem.
JSON is a data format that happens to be in a format that can be executed as code, however to do so without validating it as compliant JSON is insecure. JSON received as a string via XMLHttp, cookie, IFRAME etc must be eval'd (after validation for security). It only executes automatically if invoked via dynamic script tag.
A more accurate way to have said this is as follows:
Dynamic script loading returns Javascript code that is executed in the scope of the script that receives it... etc
I posted some thoughts on the topic and a temporary Flash-based workaround to allow cross-domain XHR in javascript at
http://blog.monstuff.com/archives/000296.html
and
http://blog.monstuff.com/FlashXMLHttpRequest
What if the XHR request in question carries out an action? For example, a page to send a password to an email address.
Your method would only deny access on response, but for a lot of AJAX calls the damage is done at the server, on request.
I realise I said that you can't tell the difference between a standard HTTP request and one through XHR, and therefore any action open to XHR is public anyway. But the difference is that in my browser I have to purposefully carry out the action (type in an address, click a link, both of which display the result of the action) whereas in your version you could make me do that without me knowing
Gareth, it is actually possible to do exactly what you suggest by submitting either a POST or GET based form to an iframe hidden in the page. I think this is what GMail does when it starts uploading your attachments before you click send.
Everyone seems to agree that cross-domain XHR is necessary - could anyone provide a few practical use cases based on actual experience? Use cases that demonstrate why the normal alternatives (maybe proxying?) are out of the question.
should be changed to
X-Cross-Domain-Access: true
The X- is the normal way to introduce new experimental HTTP headers, and it is also normal to capitalize each of the words composing an HTTP header (Content-Length, Accept-Encoding, etc etc)
And just to clarify, loading an XML document from a service that doesn't send that header, would have a similar effect to someone trying to load an HTML resource URI as the "src" attribute of an image ... it might trigger an actual http request, but the browser won't know what to do with what it got back from the server, which'd be a Good Thing™.
I also agree with this article's premise that a service exposing sensitive data over a JSON interface could be exposing themselves to serious security holes.
By the nature of the data being javascript, anything that can load a script src="" TODAY can not only load that data but also READ it. Say I have a webmail system, and I want to dynamically load my users' address books from some separate host, and that for some reason, an xmlhttprequest isn't good in this specific instance. So I proceed to expose a JSON interface to it, with a URI that returns an application/x-javascript payload. It works fine for my users and i'm happy.
Next thing I know, some punk wants to gain access to my users' data. All they have to do is create a static html web page with a script src="" tag pointing to the URI of my web service. Any user of my web service visiting their site, will basically load their address book into the context of that foreign web page. Once that happens they have the user's browser send the data in a separate request to some CGI script to capture it. BLAM. Of course, there are ways to mitigate those risks, but it's still dangerous.
I note in my post on the subject that the W3C Web APIs Working Group have been addressing the issue with a proposal posted in their mailing list forum.
As for a real-world example where the ability to cross-domain script is useful/required - Talis Whisper a totally AJAX prototype application uses Web Services from three separate sites to deliver its discovery functionality. This was only [currently] achievable with a proxy server running on the system that contains the AJAX application.
A totally unnecessary diversion of calls between browser and Web Service.
Cross-Domain JSON without XHR
Imagine a scenario where a.com and b.com are involved in cross-domain scripting. Rather than going straight to a.com or b.com with the request, the client would instead make requests to SafeDomain.com/a and SafeDomains.com/b.
Because both a and b registered in advance with SafeDomain, they are included as "part" of SafeDomain. Each would have initially signed up in much the same manner as a SSL certificate (proving who they are, etc.)
There are some obvious downsides related to centralization; however at the same time I worry about cross-domain in its current form as an attack vecotr.
Also, surely a distributed attack could be easily traced to the data publisher?