CVE-2011-3544 / ZDI-11-305 – Oracle Java Applet Rhino Script Engine Remote Code Execution

by Michael 'mihi' Schierl, @mihi42

Summary

This is a vulnerability in the Rhino Script Engine that can be used by a Java Applet to run arbitrary Java code outside of the sandbox. Since Rhino Scripts are basically strings of JavaScript, they are not controlled by the Java SecurityManager like origin of class files is controlled. Therefore, the scripting engine has to make sure that a script called from untrusted code will not be able to perform actions that untrusted code is not allowed to perform (like disabling the security manager). While the guys at Sun/Oracle spent quite a lot of time trying to achieve this, they missed (at least) one way, where you can create a script that returns a (Java) object whose toString method will run any script code in the context of the caller of the toString method. If the caller is sufficiently privileged, that script code can then for example disable the security manager and call back into your applet code to run any Java code that is in your applet with full permissions.

I won't go into detail here how you can make an applet call your toString method with full privileges – there are several ways to do so. If you don't know any, have a look at Sami Koivu's blog – he describes some of the ways in his articles about previous Java vulnerabilities he found.

Introduction to Rhino

If you have never worked with Rhino (or with the Scripting Framework), here is a short introduction. Basically, Rhino is JavaScript. But there are features in it to interact with Java code. You can bind Java objects to JavaScript variables, call methods, create new objects (both Java and JavaScript ones) and create dynamic proxy objects. The following code shows an Applet that binds itself to a script variable and then evaluates a script that builds a proxy Runnable object which implements toString and run in JavaScript. When running the example, the strings get printed ordered by the numbers they start from. Package names not starting with java. have to be prefixed with Packages., but you can also use that prefix for java. packages. Note that the expression for builing a proxy from a JavaScript object does not use the new keyword, unlike anonymous classes in Java.

GeSHi © 2004-2007 Nigel McNie, 2007-2010 Benny Baumann, 2008-2009 Milian Wolff
  1. import java.applet.Applet;
  2. import javax.script.*;
  3.  
  4. public class RhinoExample extends Applet {
  5.     public void init() {
  6.         try {
  7.             ScriptEngine se = new ScriptEngineManager().getEngineByName("js");
  8.             Bindings b = se.createBindings();
  9.             b.put("applet", this);
  10.             Runnable proxy = (Runnable) se.eval(
  11.                     "java.lang.Runnable({" +
  12.                     "  run: function() {" +
  13.                     "    java.lang.System.out.println('2 Running');" +
  14.                     "  }," +
  15.                     "  toString: function() {" +
  16.                     "    java.lang.System.out.println('4 Doing');" +
  17.                     "    applet.callBack();" +
  18.                     "    Packages.java.lang.System.out.println('6 Done');" +
  19.                     "    return 'Result';" +
  20.                     "  }" +
  21.                     "})", b);
  22.             System.out.println("1 Calling Run");
  23.             proxy.run();
  24.             System.out.println("3 Calling toString");
  25.             System.out.println("7 toString returned "+proxy.toString());
  26.             System.out.println("8 Done");
  27.         } catch (ScriptException ex) {
  28.             ex.printStackTrace();
  29.         }
  30.     }
  31.    
  32.     public void callBack() {
  33.         System.out.println("5 Callback called");
  34.     }
  35. }
  36.  

(Un-)Fortunately, this is not a security problem, even though you now have a proxy object that is implemented totally in JavaScript and therefore requires no untrusted code to run. When creating such an Object wrapper, it captures its AccessControlContext at runtime, so even if you call its run or toString method from a privileged caller, the code will still run with your restricted permissions and for example cannot disable the security manager.

The NativeError class and its flaw

When creating error objects in JavaScript, they are represented by the sun.org.mozilla.javascript.internal.NativeError class in Rhino. This class has a message property that can have assigned any JavaScript object, and a (Java) toString method that (among other things) converts this message to a string "the JavaScript way" (calling it's toString script method). Since error objects are not wrapped by Rhino or by the Script Engine, a privileged call to this toString method will not be restricted to the privileges of the applet running the script.

Surprise

On the other hand, when you build such an error object and try to call it from outside the script, you'll see a surprise:

java.lang.RuntimeException: No Context associated with current Thread
        at sun.org.mozilla.javascript.internal.Context.getContext(Context.java:2380)
        at sun.org.mozilla.javascript.internal.ScriptableObject.getDefaultValue(ScriptableObject.java:832)
        at sun.org.mozilla.javascript.internal.ScriptableObject.getDefaultValue(ScriptableObject.java:773)
        at sun.org.mozilla.javascript.internal.ScriptRuntime.toString(ScriptRuntime.java:792)
        at sun.org.mozilla.javascript.internal.NativeError.js_toString(NativeError.java:188)
        at sun.org.mozilla.javascript.internal.NativeError.toString(NativeError.java:102)

The reason for this is simple: Rhino expects every script call to be run inside of a Context - basically an object that captures your local variables etc. This context is stored in a thread local variable to be available all the time when the script is executed. Since we called an "internal" method directly, there is no context assigned to the current thread any longer, so we get this error. Adding a context directly is hard since all the context related code is in sun.* packages which are not directly accessible from untrusted code.

com.sun.script.javascript.ExternalScriptable to the rescue

So we have to examine the stack trace above more thoroughly to find a way to get our code executed without reaching the point where the context is not initialized. In fact, ExternalScriptable provides a better getDefaultValue implementation that will enter a context before calling the toString() method. A bit of digging in a debugger showed that the object that is bound to this inside the script is in fact of type ExternalScriptable, so you can reuse this object for the message property and don't have to find a way of instantiating a new one.

Conclusion

A script that exploits this vulnerability has to:

The applet has to use this script as follows:

The resulting exploit is quite short (about 20 lines of Java code and 120 characters of Rhino code), and I am sure you can make it shorter if you really try. However, I don't want to make it too easy for criminals to leverage this vulnerability just by copying and pasting; therefore I won't publish my exploit code in the near future. (Yes I'm aware that someone else will most likely build a Metasploit module from it, but then it was not my code that got ripped by the criminials...)