raven ioctl

Rhino security.

ioctl.org : jan's home page : javascript : rhino security

Warning! Lame and cheesy hack!

I recently had the opportunity to embed Rhino into a third-party web application. The purpose was to permit users to upload (untrusted) scripts. Those scripts could then run with the privilege of the user in question, enforced by the security model inherent in the webapp's API. It all seemed too easy to be true, until I uploaded a script that began with:

	var CLASS = embeddedObject.getClass().getClass();
	var FILE = CLASS.forName("java.io.File");

...and it went downhill from there rapidly.

The problem

The problem is/was that I'm trying to use javascript as a "little language" to permit relatively untrusted third parties to upload and execute javascripts within a web application: their entire purpose is to coordinate a few calls on the objects exposed by the web app.

I want to expose some of the web application's operations to the javascript by embedding objects into the Javascript context. One possibility is to explicitly implement org.mozilla.javascript.Scriptable on classes that wrap the application's objects. Unfortunately, the object graph reachable from one of the embedded objects covers members of quite a few classes; and I wanted to avoid having to wrap all of those classes in Scriptable implementations by hand. One possibility would be to generate those classes semi-automatically, either at runtime or compile-time. In fact, Rhino already implements the runtime wrapping itself, which led me to the solution below.

Now, those web application objects require certain levels of security privilege (reading files, writing files, etc) for particular operations. There's no getting around this; that's just the way the webapp was written. The privileged operations live in a few localised places, but nevertheless, making large and invasive changes to the web app (someone else wrote it) wasn't really feasible. For instance, sprinkling doPrivileged() calls throughout it would be problematic. And it wouldn't actually be the end of the problem.

Additionally, because the webapp runs under a traditional security manager (tomcat's), I was limited in what I could do without doPrivileged(), since if I were to slap a restrictive policy on Rhino then the embedded application objects are also crippled when called from javascript. (This is the whole raison d'etre of doPrivileged.)

The last part of the problem is this: even without a restrictive security policy in place, the embedded objects colaborate to enforce the programmatic security model of the web application. That's a good thing; those privileged classes I mentioned rely on this to permit or deny those pesky file operations. This enforcement, however, effectively uses objects as capabilities (if you can reach the object you can perform the operation); thus, unscrupulous code could create new capability objects (by simply calling new), effectively circumventing the cooperative security model in the webapp's API. All too many classes had public constructors; again, I was looking at extensive (and possibly unworkable) changes.

Rhino is quite clever. When you place an object into one of its Javascript contexts, it wraps it up for you, exposing all its public members. And that's the issue.

Unfortunately, rhino exposes the getClass() method on those objects when it wraps them, and from there it's a relatively small step to being able to access all the java reflection APIs via javascript. A security policy that turns off the runtime permission getClassLoader, accessDeclaredMembers and accessClassInPackage.* unfortunately cripples Rhino, because it uses those permissions internally in order to provide its object-wrapping.

The workaround

Anyway: getClass() seemed to be the major stumbling block. From there, the reflective world is your oyster. So I made a couple of small changes: firstly, removing getClass and the other org.mozilla.javascript.NativeJavaTopPackage objects installed by org.mozilla.javascript.ScriptRuntime#initStandardObjects; secondly, by a gross hack in org.mozilla.javascript.JavaMembers#reflect that simply adds:

     if ("getClass".equals(name)) continue;

at the appropriate spot. The resulting (rather neutered) Rhino appears to still permit me to expose to untrusted scripts the functionality I'm after; and I _think_ it's relatively safe. I added a completely restrictive ClassShutter implementation to be on the safe side.

Limitations of this workaround

If your application has a nice component model, chock-full of factory methods (where appropriate) and you can get at all the functionality you need by following the object graph from a few key starting points, then you're onto a winner with this.

On the other hand, if your API requires extensive use of new in the client code in order to operate, then (a) shame on you! and (b) you're out of luck. Fortunately, the webapp this story all sprang from fell into the former category.

A final note: aspects

It's worthwhile adding that the interception of object creation and method calls was really what I was after here. Doing this within Java's extant security framework would have required a great deal of effort (and might actually have proved impossible given some of the other constraints). What I really could have done with, of course, was a way to instrument code (possibly at runtime), including (perhaps) the core Java classes, to permit me to let Rhino call the bits of functionality I was interested in exposing - and no others. And that's what aspect-oriented programming is all about. In fact, I've occasionally thought (whenever I think about AOP, which isn't often) that the security scenarios are far more interesting and compelling than the other old AOP chestnut, logging. The danger is always that security considerations (which are one of those cross-cutting concerns) are complex and the separation of the security can lead to it rotting, just as much as it can mean that it's all in one place for ready inspection. Swings and roundabouts, I think. But I'll probably revisit this with an AOP solution if I can be bothered to sort one out. (My next goal is to do something similar to what I've outlined here for SnipSnap, the object model of which is not so clean-cut, so I may be falling back on AOP sooner than I'd otherwise anticipated.)

Another way to do it? metadata

It's an invasive approach, but something else has just occurred to me: the Rhino wrapper-generation classes could readily be modified to look for Java 5.0 decorations on classes and methods. Again, that would permit the controlled exposure of _only_ the desired methods. The problem with this is that it means you can't have a method return a java.util.Anything because you don't get to attach metadata to classes you don't write yourself.