Introduction to Caja, Part 1: Elementary Gadgets

What is Caja?

If you are the kind of guy that runs websites with user content on them, then Caja is a system that lets you allow that content to include Javascript safely. Or, if you already do allow Javascript, more safely.

If you are the kind of guy that puts Javascript on other people's websites (say, a gadget author), then Caja is a system that you may have to use to publish the gadget at all, or it may be that its use is optional but gives you some benefits (like you can do more, or your gadget needs less manual review).

Caja allows the author of the container to completely control what gadgets do. The container is the page that hosts the gadgets, which are typically snippets of HTML, CSS and Javascript written by untrusted third parties (that is, neither the owner of the container, nor the viewer of the page). Caja allows the gadget author to use entirely standard Javascript, with a few minor exceptions, but restricts it so it cannot do anything the container did not intend it to.

Getting Off The Ground

There are all sorts of different scenarios in which you might be exposed to Caja, but I always think the less extra stuff is invovled, the clearer things are, so the first thing I want to do is show you how to play with Caja without a whole pile of infrastructure. In later articles, I'll show you how to set it up yourself, but for this one we'll just look at what Caja does and how you interact with it as a gadget developer.

The very first thing we need is a browser and Javascript debugger. I like to use Firefox and Firebug.

Once you have those set up go to the Cajadores testbed (this is a site the Caja development team maintains). You should see something like this...

The two boxes to the left allow you to define a couple of gadgets that run in our testbed container.

This particular container has a few aims in mind. It wants the gadgets to

Because it is only a toy container, it doesn't offer much actual functionality. But to give you something to shoot at, it does contain a secret function you should not be able to call. Here we are, failing to call it

So what's going on here? Well, first of all we fill in some code for the first gadget (we don't need the second one at this stage). The code is pretty simple, just

<script type="text/javascript">
loadModule("keystoneKop").f();
</script>

As you can see, this goes in the big text box. Once we've done that, we hit the "Cajole" button and the result is what you see in the screenshot (you'll have to enable Firebug for this page, of course). The result is that the Caja compiler is invoked, and the resulting code is executed. We can see the output of the compiler on the right (we call the process of compiling "Cajoling"), and the error down at the bottom.

What's this all about? The Caja compiler has taken our rather simple piece of Javascript code and turned it into the rather more convoluted Javascript on the right. I won't get into what exactly that's all about at this stage, but the thing is that the output enforces security properties on the Cajoled code, using nothing but standard Javascript. This means Cajoled code can run in any standard browser, without any need for plugins.

One of the properties that is enforced is that methods on imported Javascript objects are only accessible if the container has explicitly allowed them to be accessed. So, the reason we get Not callable: ([Object]).f is because the container did not give us permission to call f. If we had managed to call f then we would have got an alert, since the keystoneKop module is defined like this (you can find this in testbed.js):

/** A registry of the public APIs of each of the testbed applets. */
var gadgetPublicApis = {
  // Predefine a honeypot so we can try to exploit confused deputies
  'keystoneKop': ___.primFreeze({
        // Not marked simple.  It is a breach if a gadget can get the container
        // to call this on their behalf.
        f: function() {
          alert('You get a cookie ' + [].join.call(arguments, ', '));
        }
      })
};

It's called Keystone Kop, by the way, because if you succeed in running the function then there's a confused deputy at work.

OK, so now we know what we (hope we) can't do, what can we do? Well, quite a lot. In fact, pretty much anything you can do with normal Javascript - at least, anything that isn't evil for a piece of embedded code. So, for example, we can display some HTML, using perhaps a little CSS, and then dynamically modify it. Let's try that:

<div id="i1">Some text</div>
<script type="text/javascript">
document.getElementById("i1").innerHTML = "Hello, world!";
</script>

If all has gone to plan, then the red dotted box to the right should contain "Hello, world!", as the code above told it to. But this isn't very interactive. Let's have a button that does something:

<div id="i1">This won't be here in a moment.</div>
<button onClick="javascript:document.getElementById('i1').innerHTML
      = 'See? Gone!';">Click Me!</button>

Being Evil

Not exactly Earth-shattering so far, but now let's think about an evil gadget. An evil gadget might try to get the user to do something foolish by changing the instructions. If you poke around the page source you'll see the instructions live in a DIV called "instrs". Let's write an evil gadget that changes them. First of all a test version to make sure we've got the everything right...

<div id='i1'>Dummy instructions</div>
<button onClick="javascript:document.getElementById('i1').innerHTML
= 'Step 1: Send your username and password to <a href=&quot;mailto:hacker@evil.com&quot;>hacker@evil.com</a>';">Be
Evil!</button>

If you run this and press the button, you'll see that our plan for world domination is taking shape. Muahaha! But ... hold on a second ... if you look closely at the text "Step 1: Send your username and password to hacker@evil.com" you'll see something isn't quite right - "hacker@evil.com" should be a link, but it isn't. A closer inspection (hint: right-click and choose "Inspect Element" if you're using Firebug) reveals that our <a> element has been altered. It now reads:

<a target="_blank">hacker@evil.com</a>

What happened? Well, you might think you are writing directly to the document element's innerHTML properly (i.e. the text displayed, in this case), but actually if you look closely at the Cajoled version of the code, you'll see that

...innerHTML = ...

has become

x0___.innerHTML_canSet___? (x0___.innerHTML = x1___): ___.setPub(x0___, 'innerHTML', x1___);

It turns out that innerHTML_canSet___ is not set, so ___.setPub is called. This has been told to translate setting innerHTML to a call to an HTML sanitizer, which has removed the href attribute from the <a> element and added the target attribute.

But no matter, I'm sure the obedient user will follow our demands and type in the email address himself: what is a little user inconvenience when the Universe is within our grasp? Let us weaponise this beast

<button onClick="javascript:document.getElementById('instrs').innerHTML
= 'Step 1: Send your username and password to <a href=&quot;mailto:hacker@evil.com&quot;>hacker@evil.com</a>';">Be
Evil!</button>

Try it. Press the button, and glory as the instructions change to our evil version. But wait! They don't change! And we get an error...

x0___ is null
(?)()()%2BytBDb...yTw%3D%3D (line 9)
plugin_dispatchEvent___(button, click clientX=732, clientY=453, 0, function())domita.js (line 1080)
onclick(click clientX=732, clientY=453)RLRFZOKU...gqw%3D%3D (line 2)
[Break on this error] ...: ___.setPub(x0___, 'innerHTML', x1___);

What has gone wrong? Well, the document global, which we expect to reference the root of the DOM tree, has been replaced by a local variable:

var document = ___.readImport(IMPORTS___, 'document');

This imported object actually restricts us to only the DOM that was included in the gadget itself, rather than the entire DOM. we have no access to the rest of the page. So, sadly, we are foiled in our attempt to phish our innocent victim, or, indeed, mess with the container in any way.

Communication

Caja isn't only about restricting gadgets: it also enables them to do more. In particular, it is possible for a gadget to create an object that can be sent to another gadget safely. To illustrate this point, let's create an object one gadget can export that another can modify parts (but not all) of. For the sake of argument, we're going to offer the user a fruit. The second gadget can change which fruit, but not how we ask the question.

Here's the code for the first gadget.

<div id="question"></div>
<script>
function Question(prefix, fruit, suffix) {
  var ret = caja.freeze({setFruit: function(newFruit) {
      document.getElementById('question').innerHTML = prefix + newFruit + suffix + '?';
    }
  });
  ret.setFruit(fruit);
  return ret;
}
exports.Question = Question('May I get you a ', 'banana', ', sir');
</script>

In it we create a constructor for a Question object. The constructor returns an object that has just one property, setFruit, which is a function that can be called to change the fruit we ask about. Before that object is returned, we call the function just to get something displayed initially.

The gadget then creates one of these Question objects and exports it.

The second gadget merely imports the object and calls the function to change the fruit. This code should go in the second gadget box, and should be Cajoled after the first gadget has been Cajoled.

<script type="text/javascript">
loadModule('gadget.1').Question.setFruit('grape');
</script>

Because we used caja.freeze on the object, the second module can't change any of its properties - in particular, it couldn't sneakily change setFruit to set some other text, in this case. Nor can it get at the private suffix and prefix.

Conclusion

In this article I've introduced Caja, shown you how you can play with it on your own, demonstrated a few ways that Caja protects both the container and your gadget, and illustrated inter-gadget communication using objects.

In the next installment, I'll show you how to to set up your own container.