Saturday, 2 February 2013

Fun with Java Serialization and Reflection

Last year I started to have a poke at Java for security vulnerabilities, I am not really sure why, but probably because I was having some success breaking .NET and felt Java was likely to have similar issues. Shame I picked a bad time to do so if I wanted to be famed for owning Java (re: Security Explorations). Still I think I found a few things Adam Gowdiak didn't find :)

Anyway, with the recent fixing of my last Java vulnerability in 7 update 13 (CVE-2012-3213 if  you care) I felt it was a good time to describe what it did and how it worked, especially as it is a mixture of the classic Java serialization vulnerabilities mixed with the hot topic of reflection, making it an interesting vulnerability. It will also describe another source of access to protected package Class objects.

The underlying issue is in the Rhino script engine. This is a Javascript interpreter built into modern versions of the JRE (from version 6 onwards) and originally comes from a Mozilla project where it was primarily designed to run in a fully trusted environment. It has had security issues before (for example see CVE-2011-3544) as Sun/Oracle decided to make it work in a sandboxed Java environment. However to exploit what I found you have to be a bit creative.

A Quick Overview of Rhino Security

As the script engine could potentially create user-defined but trusted code one of the things that was added to the engine was some checks to prevent sandboxed code from calling into objects which might have dangerous side effects, for example gaining access to protected packages such as 'sun.*'. In order to access a native Java object the engine must first wrap it with a scriptable wrapper by using a WrapFactory. The JRE provides a custom implementation in com.sun.script.javascript.RhinoWrapFactory which does these checks before the Javascript is allowed to call methods on that object. In that code it checks things like whether the object is a ClassLoader (which might allow the script to bypass package access checks in loadClass etc.), it also checks for the package name of Class objects and classes to see if they are visible to scripts (ultimately calling the current security manager's checkPackageAcccess method). There are some exclusions though, because the core Rhino classes are actually embedded within the sun.* package which means it can at least get access to those. At any rate what this ultimately results in is I couldn't see an immediate way of using the script engine to call into protected classes to do something nasty.

Bug Hunting in the Javascript World

The fact that these wrapping mechanisms are needed are a good example of some of the differences between Java and Javascript. You could consider Javascript to have one of the most flexible reflection implementations, all objects are reflectable (well of course depending on the implementation), for example to determine what properties and functions an object supports you can do something as simple as:

for(x in obj)
{
    System.println(x+": " + typeof(obj[x])");
}
You can dispatch methods or read properties by just using the obj[x] syntax. The Rhino script engine aims to  replicate this functionality even for native Java objects by providing isolated scriptable wrappers around common reflection primitives such as methods, fields and constructors. You can find these under the sun.org.mozilla.javascript.internal package with classes such as NativeJavaConstructor and NativeJavaMethod. The interesting thing I noticed was these were not performing any further reflection checks on the classes they were interacting with, presumably if you could get access to one of these classes you must have already gone through the object wrapping process and that would have blocked the package access. And so it seems, after a bit of digging I managed to find the syntax for getting access to the constructor object using the following code:

importClass(Packages.sun.swing.SwingLazyValue); 

SwingLazyValue['(java.lang.String,java.lang.String,java.lang.Object[])'];

This would get you a constructor on the SwingLazyValue class (which is a useful execution pivot in the JRE to call internal static functions, especially as it is based on a public interface we can access and call through). But if you try this in a sandboxed environment it fails with an exception due to the package access of the Class object, so close but so far. Still there is clearly a way of exploiting it otherwise I wouldn't be documenting it.

Serialization to the Rescue

If you look at the class hierarchy of the NativeConstructor class you will notice something interesting (or at least you would have done prior to update 13), it implemented the Serializable interface. So perhaps instead of using Javascript to access the constructor we could instead use serialization. The advantage of this approach is we might be able to reconstruct the object in native Java code first (which won't necessarily complain about it) and then pass it back into Javascript for the final exploitation.

I knocked up a simple full trust application which would capture a NativeConstructor object, serialize it then check it deserialized correctly. I went to run it, but it threw an exception because some internal fields could not be serialized. Damn... Looking through the documentation it looks like serialization was a vestigial feature of the original Mozilla implementation, Sun had not bothered to ensure it still worked correctly, so I followed a hunch, perhaps the object doesn't actually need those unserializable fields, perhaps I can just remove them. Fortunately Java makes it relatively easy to do this by implementing the replaceObject method on the java.io.ObjectOutputStream class.

A few minutes later I had:

class MyObjectOutputStream extends ObjectOutputStream {
   public MyObjectOutputStream(OutputStream stm) throws Throwable {
       super(stm);
       enableReplaceObject(true);
   }

   protected Object replaceObject(Object o) {
       String name = o.getClass().getName();
       if(name.startsWith("com.sun.script.javascript.")) {
          return null;
       }
       return o;
   }
}

Running this in my full trust application my hunch was proven correct, the script didn't in fact need those internal fields to work and the NativeConstructor object could be used freely when deserialized. Feeling the end was in sight I plugged it into an Applet, I took the binary output from the full trust application and deserialized it, I was dissappointed to be greeted with:

java.security.AccessControlException: access denied 
   ("java.lang.RuntimePermission" 
    "accessClassInPackage.sun.org.mozilla.javascript.internal")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java)
at java.security.AccessController.checkPermission(AccessController.java)
at java.lang.SecurityManager.checkPermission(SecurityManager.java)
at java.lang.SecurityManager.checkPackageAccess(SecurityManager.java)
at sun.applet.AppletSecurity.checkPackageAccess(AppletSecurity.java)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java)
at java.lang.ClassLoader.loadClass(ClassLoader.java)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at Demo.doExploit(Demo.java)

So I seemed to have gained nothing, I traded a package access check for one type to one from the Rhino implementation. Still all was not lost, I had a plan.

Ask and You Shall Receive

Now if you look at the stack trace of the exception one method frame is clearly responsible for it, the java.io.ObjectInputStream.resolveClass method. If you go and look at the implementation of that it is passing the current class loader into the forName method, which in an Applet is the AppletClassLoader which doesn't much care for handing out references to sun.* package classes. Still serialization in Java (compared to .NET with which I have bit of experience) is an unprivileged operation, even sandboxed code can do it, and there are also some things you can do to modify the process of serialization to do some funky things, like overriding the resolveClass implementation. So this led me to the realization that perhaps if I could get the protected Class objects from somewhere else then I could implement my own ObjectInputStream, override the resolveClass method and return what I needed. So I put together:

class MyObjectInputStream extends ObjectInputStream {
   Hashtable dict;

   public MyObjectInputStream(InputStream stm, Hashtable dict) 
          throws IOException {
      super(stm);
      this.dict = dict;
   }

   protected Class resolveClass(ObjectStreamClass clazz) 
         throws Throwable {
      
      if(dict.containsKey(clazz.getName())) {
          return (Class)dict.get(clazz.getName());
      } else {
          return super.resolveClass(clazz);
      }
   }
}

This class would take a Hashtable containing some classes, if we already had the required Class object we could return it as is so we don't get the security exception, but of course this begs the question, how will we populate the dictionary? Well the key is in serialization itself.

We already know that the the NativeJavaConstructor class is serializable, we also know untrusted code can perform the serialization process and that untrusted code can create the NativeJavaConstructor objects as long as they point to non-privileged classes. So can we not use the serialization process against itself to get access to all the classes we need?

Turns out we cannot directly use the ObjectOutputStream with the overloaded replaceObject method as that is actually one of the few privileged operations in the serialization process (something to do with access to private fields or something). Anyway so we will go to the source of how ObjectOutputStream determines what to serialize, the ObjectStreamClass class. You can call this from untrusted code and it will return you a description of the object, including the classes of the object's fields. From this you can take copies of the classes and keep them for the input stream to use. Such as:

public void captureTypes(Object o) {
   try {
       Class c = o.getClass();

       while(c != Object.class) {
            dict.putClass(c);

            ObjectStreamClass stmClass = ObjectStreamClass.lookup(c);
            ObjectStreamField[] fs = stmClass.getFields();

            for(int i = 0; i < fs.length; ++i) {
                Class fc = fs[i].getType();
                dict.putClass(fc);
            } 

            c = c.getSuperclass();
        }

  } catch(Throwable e) {}
}

This fills in the dictionary and you are good to go. And amazingly this does work... From that I could do things like create my SwingLazyValue which would call a static method such as SunToolkit.getField and that was the end of the road for the sandbox. Of course how you actually go about getting the sun.swing.SwingLazyValue Class object is left as an exercise for the reader ;)

Conclusion

Well it looks like the fix was simple in 7u13, anything within Rhino which could be serialized now cannot which considering they never were without substantial effort I guess isn't an issue from a break-compatibility point of view. But it does once again show that package access restrictions, especially with serialization are not adequate to protect Java from itself.

1 comment:

  1. it's pretty amazing how many more bugs have been found since gowdiak raised 50 issues. it looks like javafx had lots of Method#invoke / Class#forName / Class#newInstance unprotected :) i wasn't even aware these classes were available in applets. i'm still trying to work out the sound and Java 2D exploits :(

    ReplyDelete