Frame-Busting Gadgets

In the questions after my @media ajax talk, Simon Willison asked about frame busting. If gadgets sit inside iframes, what’s to stop them from busting the frame, i.e. replacing the container with another website. I notice he made a similar comment when OpenSocial came out. If a gadget can cause iGoogle to go away in place of another website, it could for example launch a phishing attack by presenting a mock iGoogle login page (“iGoogle has timed out. Please re-enter your username and password.”).

At a technical level, there is actually nothing to prevent this. There’s no magic in the container. IFrames are about as good as it gets in modern browser for taming the gadgets, but there are still problems with that model, and one of those is the ability for iframes to bust out. I decided to create a demo of this. The gadget is here. You can drop it into iGoogle and see for yourself. Video below:

[kml_flashembed movie=”http://picupper.com/video/framebust.swf” height=”700″ width=”400″ /]

(Parenthetically, the video is vertical. Why are videos always horizontal on the internets? It’s a computer, not a TV! It should be just as easy to embed an dodecahedron video if that’s what I fancy at the time.)

At this stage, there’s no technical way around this. One possibility would be for the container to do something in the rarely-used onexit() method, which runs just before the new page is loaded. However, as I learned with WebWait, which is also vulnerable to frame-busting, there’s nothing you can really do at that stage, and you can’t determine if the exit is happening because of a frame-busting event as opposed to the user just typing in a new URL. I suppose onexit() could still be used to log info to the server – if you knew all the times the container exits and which gadgets were present at that time, you might find certain (malicious) gadgets were present more times than you’d expect if they were present randomly. Although, I’m not sure about how reliable it is to make an Ajax call in the onexit(). (In this case, though, it would only need to work some of the time.)

In the future, the technical solution is Caja, if it proves to be production-ready. Caja ensures a safe subset of Javascript, so a container like iGoogle would only ever serve a gadget if it was verified to be safe.

Until then, we have to rely on “social” mechanisms. i.e. user comments in the catalogue and manual checking by container staff. A while ago, iGoogle made it harder to add a gadget directly by URL – you basically have to use the catalogue unless you’re a developer. So the catalogue is effectively acting as a whitelist – once found to be malicious, a gadget could be removed. This isn’t simple though – gadgets are dynamic and the catalogue is effectively just a list of URLs. A malicious author could have their gadget added to the catalogue and then change it, so the gadgets need constant review – a highly ineffective process.

I also mentioned in the presentation that “html” type gadgets are more recommended than “url” gadgets. This is partly because with html gadgets, the container at least has some idea of what’s happening inside the gadget – though not entirely, since the code can still reference external scripts and services. With “url” gadgets, what happens is entirely controlled by an external server.

Relative Paths and On-Demand Calls in Gadgets

A problem with the current opensocial gadget spec is that there’s no relative path support. This means you end up hard-coding any references to Javascripts, CSS stylesheets, images, and services which are distributed along with your gadget.

This is not good. For example, you may have a “prod” setup and a “dev” setup. While developing, you will have to hard-code <script> tags and so on to hit the “dev” location, but then for production, you will want them pointing at your scripts in the production location. Likewise, you might want to ship the bundle to someone else for them to host on their own network.

You can deal with it using a pre-compiler tool – you know, using sed-like token substitution to piff in the right location. But a pre-compilation step is a messy compromise – I just want to distribute the code and be done with it. Another solution would be to output the gadget spec from a script, which would let you solve the problem in one of two ways: (a) inline common Javascripts and stylesheets so that you don’t even need to link to them; (b) piff in the right location on the fly. Again, this is not ideal. It introduces a new server-side language and demands the spec server supports it. My aim here is simple distribution of gadgets – as long as you can host a tree of static files, you can serve my gadgets.

The optimal solution right now is far from ideal, but is the best way I know to do it. You use On-Demand Javascript (and on-demand CSS and on-demand <insert resource type here>). First, you work out the gadget spec location, then you manipulate the URL to find the required Javascript, and then you pull it down using DOM manipulation.

I’m using a compressed version of something like this:

javascript

  1. var gadgetURL = _args()["url"];
  2.     function loadScript(scriptURL, onLoaded) {
  3.       var baseURL=gadgetURL.replace(/[a-zA-Z0-9_]+.xml(?.*)?/,"")
  4.       if (scriptURL.indexOf("http")!=0) scriptURL = baseURL + scriptURL;
  5.       if (debugMode) scriptURL+="?" + (new Date()).getTime();
  6.       var script = document.createElement("script");
  7.       script.src = scriptURL;
  8.       if (onLoaded) script.onload = onLoaded;
  9.       document.body.appendChild(script);
  10.     }

It’s partly based on a mailing list comment from Arne Roomann-Kurrik. Massaging further, I end up with the following boilerplate block which must be cut-and-paste into each gadget to bootstrap reuse:

javascript

  1. _IG_RegisterOnloadHandler(function() { var script = document.createElement("script"); script.src = _args()["url"].replace(/[a-zA-Z0-9_]+.xml(?.*)?/,"") + "../core.js"; script.onload = initialise; document.body.appendChild(script); });

After dropping this at the bottom of my gadget, I know that (a) ../core.js will be pulled down and executed – it is relative path from wherever the gadget came; (b) initialise() will be called once core.js has been loaded. Each of my gadget’s has an initialise() function which does gadget-specific initialisation. core.js is a common library and also happens to include loadScript() in case I want to pull down further Javascripts, and also loadStylesheet() so I can grab a stylesheet with relative path.

All this is not at all ideal, for several reasons:

  1. For all the benefit of neatly using a separate Javascript file, you have to cut-and-paste boilerplate code into every page! But at least the boilerplate won’t have to be changed often.
  2. Performance will suffer as the script is loaded later than it should be.
  3. It relies on the “url” being passed into the gadget; to my knowledge, this is not something you can definitely assume will happen, i.e. it’s not mandated by the opensocial standard that the gadget receives a parameter called “url” identifying where the gadget came from.

I have requested on the opensocial spec mailing list to provide direct relative path support and the response has been positive, hopefully we’ll see it soon, thus rendering large parts of this post obsolete. The solution looks to be rather neat – injecting a <base> tag to establish a base URL which all relative paths work against.