26 August 2008

Xsstc: Cross-site scripting through CSS

I've been doing a lot of reading on cross-domain scripting approaches. Generally speaking, the browser is sandboxed by the same-origin policy, and mashups that want to incorporate data from external sites, even if those sites are cooperating, need to provide server-side proxies. There are a couple of popular workarounds: (1) using the hash (#) portion of the URL, which can be read between frames, and (2) cross-domain JSON, or in other words, directly importing live scripts from a third party site into your own. Other more fanciful techniques include using the Flash plugin; obviously this fails if you try to run the code on any device without Flash installed, regardless of its Javascript capabilities (the iPhone comes to mind).
Ideally, a client script just wants to directly invoke a server-side method and get a response back. Due to popular demand, there's work underway in the standards bodies to make this happen, but it will be a long while before it reaches ubiquity.
I started to wonder about other pieces of data in the browser that might enable the basic use case, and after some long hours of experimentation, I finally found a way in: externally loaded cascading style sheets (CSS).
It turns out CSS leaks data in a very subtle way. Properties set by an external stylesheet (that is, one that is loaded using a LINK REL="STYLESHEET" tag) are used to style the elements of the host page, and at runtime the page can introspect itself to see what styles have been applied. Most of these tend to be strictly prescribed data, such as background colours for block elements, or some multiple choice items, like left/center/right alignment for text. While you could conceivably come up with a binary (or ternary) system based on that, it would be a pretty nasty job to try to make those into a general-purpose data channel. Fortunately, there are a few places where CSS lets you specify essentially free-text attributes: image URLs.
n.b.: I did a lot of searching on the topic but it was only after I got this technique working that I found the proposal posted by Gideon Lee on the OpenAjax mailing list, advocating much the same approach. I'm not sure if that work is still in progress as the last message on the list dates from October '07, but Gideon deserves credit for coming up with the basic idea.
I chose to work with the background-image attribute, and verified that a location hash for an image URL set in the CSS, though meaningless to the browser, is still visible by introspection via the getComputedStyle() method (currentStyle attributes in IE). There's some complexity in reliably reading this value, and in dynamically loading stylesheets, but the long and short of it is that on top of this system I've created a cross-browser Javascript library for cross-site requests.
First, check out the test page I've set up. You might want to view source, and also check out the two CSS "response documents" it references. Then read on for how to do it yourself.
The Client
Using the library is straightforward. You can get the current version at http://www.tralfamadore.com/xsstc.js, or a minified version that's a mere 777 bytes at http://www.tralfamadore.com/xsstcx.js. Stick it on your server somewhere or feel free to link to the copy here directly.
On your page, you need the following:

A SCRIPT tag in the header referencing xsstc.js (or xsstcx.js)

An empty DIV tag in the body with id="Xsstc". No other attributes required.

Javascript that calls Xsstc.exec(functionURL, callback). This method loads the specified URL and expects it to be formatted as described below (The Server). Once it has finished loading, it calls the specified callback function, which takes one argument, the string containing the parsed response.

The simplest HTML page looks something like this (using the HelloWorld example from the test page):

The key pieces are bolded above. In this example, http://lbs.tralfamadore.com/test.css serves as the server-side endpoint. Now let's look at...
The Server
The server's job is straightforward. It receives a normal HTTP GET request, that might have various arguments (these can be encoded in the usual way, via query string parameters, pathinfo, or whatever you like; Xsstc doesn't prescribe the notation), and must respond with a valid CSS stylesheet document.
The trick is in embedding the method response in the CSS background-image value. I've found that "about:blank" (which causes the browser to show a blank screen) is a good placeholder value for a background image that the response can then be appended to after the hash (#) character. If you use a real image URL, it will most likely get loaded by the browser, which isn't really what we want. The response proper needs to be URL-encoded, as it's, well, part of a URL.
In order for the client side to read the value that gets set by the stylesheet, it needs to attach to an actual element in the HTML document. That's why we added the do-nothing DIV called Xsstc. The CSS simply targets this DIV by its ID, and sets its background image. So a response document that wanted to embed "Hello World" would look like this:

#Xsstc {
background-image: url('about:blank#Hello%20World');
}

Because this response format is so simple, it's easy to create in just about any server-side programming language. And because of the data seepage inherent in stylesheets, the server can be any site on the Internet that has chosen to expose its services in this way — you're not limited by the same-domain policy the browser applies to other external requests.
Xsstc and JSON
There's a natural fit between Xsstc and JSON, as one of the examples on the test page alludes to. I've taken a sample JSON response straight off of www.json.org/example.html, URLencoded it, and slapped it into the Xsstc response format. This is not to say that Xsstc is dependent on JSON in any way: the Xsstc.exec() method generates a callback that returns whatever string is in the response, however it's formatted. But JSON is a nice compact way of representing datasets that can be easily worked with in Javascript, so a JSON library on top of the Xsstc communications channel seems like a natural fit.
Compatibility
This is the first release and while I'm sure there will be something broken, I've tested the examples (minimal though they are) on recent versions of Mozilla, Safari and Internet Explorer (IE is of course the worst to work with, but with a little bit of switching logic it seems to be doing well). It should also work on modern versions of Opera, and hopefully anything else that's W3C compliant.
Limitations
There are a few limitations that are worth being aware of. The first is that because the response string needs to be embedded in a URL, some browsers (you know which) are likely to cap the possible length of a response. While there are ways to work around this (for example, you could split one response into several consecutive method calls), it might mean that Xsstc is overly painful for implementing methods that have a bulky response.
At the moment the library is also single-threaded, though this can be remedied in time. This means that only one Xsstc.exec() method can be in progress at a given time, or you're likely to have untoward side effects. In a similar vein, there's virtually no error-handling going on in the current version, and the script will happily wait until the end of time for a response from a server that might be down.
Finally, because there's no onLoad event for stylesheet loading, the script is set up to poll for the availability of the response. In my tests this hasn't caused significant problems (there's a 50ms pause between each check), but sites that have a lot of other activities going on may want to look at how to best tune the performance of the timers.
Security
My understanding is that you cannot specify javascript: URLs for CSS background-image attributes and expect them to execute, which should mitigate any concerns of remote scripts stealing data. In the OpenAjax discussion, it was mentioned that on FF2 you can apparently execute javascript in this mode but it runs in a very sandboxed manner, without access to the document object. Taking a broader view, if there is a vulnerability here, it exists already with the ability to load foreign stylesheets, and will not be something new exposed by Xsstc.
Because both the client and server systems must cooperate on the request/response cycle, and the Xsstc DIV element is the only item singled out for data transfer, there's very little likelihood of "rogue" code. In addition, Xsstc doesn't rely on script loading and is therefore naturally immune to the trust issues that plague cross-domain implementations of JSON.
License
Xsstc (pronounced, if you'll indulge me, "Ecstasy") is licensed BSD-style. The relevant text is in the xsstc.js file. I'm happy to incorporate worthwhile changes and additions — just reply in the comments or email me (my address is wes at this blog's domain).
As always, happy hacking!

7 Comments:

Wonderful hack! Thanks for sharing with the community. I'm sure we will see a widespread use of Ecstasy, and this time it won't damage any brains (or maybe yes, I already started thinking how to put the library to a good use :P).Happy hacking!

The one advantage CSS has is that there's no need to trust the remote site. In standard cross-domain JSON, you're allowing the remote site to execute arbitrary code and trusting it not to do anything malicious.

In practice, that's often a more theoretical worry than a practical one, I agree (but all it takes is one good man-in-the-middle attack). Xsstc just gives you another option.

I think you need to take a closer look at this. This looks very similar to an XSS attack implemented by "Sammy" against MySpace where he embedded javascript as the URL for the background image of some div on his profile. This was done in the style attribute of the div and not in an external CSS file but I can't see any reason why it wouldn't work here:

See:

http://fast.info/myspace/

The basic idea:

<div style="background:url( 'javascript:alert('xss')')">

(he needed a LOT Of extra work to beat the code validator but you get the general idea).

@adam -- interesting. If that's the case, then in those browsers that are insecure, there's no security advantage over standard script includes.

I haven't played with various scenarios enough to know if there's a way you could set it up so the URL in the CSS does not actually get invoked/loaded. If so there might be a get-out-of-jail-free card for that issue. IIRC it didn't work to have just the hash and response value, e.g. "#HelloWorld", there had to be a reasonable approximation of a URL in front of it.

Alternatively you could look for a CSS text element that didn't rely on being a URL. The only ones I saw were the before and after pseudoclasses, but I was not able to read their values using Javascript (I think that's a widespread browser bug, but all the same, it doesn't work). Maybe someone more clever than me can find a more viable attribute to subvert...