Thursday, April 08, 2010

Java Trusted Method Chaining (CVE-2010-0840/ZDI-10-056)

Relevant Identifiers:
ZDI-10-056, CVE-2010-0840


Impact:
Privilege escalation of the Java security sandbox. The most obvious target would be the Java Runtime running Java Applets, JavaFX and Java Web Start applications in a web browser. Untrusted code will gain full privileges of the user executing the browser process.


Oracle Java Patch:
http://www.oracle.com/technology/deploy/security/critical-patch-updates/javacpumar2010.html


Steps to remedy:
Update to Java 6 update 19


Details:
Java code originating from the Java core classes in the JRE/lib directory is considered trusted.

Java code originating from an unsigned Java applet is considered untrusted.

When Java evaluates privileges for any given operation, it considers the whole method call stack.

For each item on the stack, the privileges of the class that defined the method in question are considered. Thus, if class Foo defines method call() and class Bar is a subclass of Foo, and an instance of Bar is on the stack, and the method is call(), the privileges of Foo, not Bar, are considered.

It is possible to implement interface methods with methods inherited from a superclass; For example, if interface CacheItem has void method delete() and class Folder, which implements no interfaces, has a method with a compatible signature, it is possible to create a concrete class FolderPosingAsACacheItem which extends the Folder class, and implements the CacheItem (with the inherited method) without defining any methods of its own.

There are hundreds (thousands?) of trusted classes in the Java core that call methods on objects which can be defined by untrusted code either directly, via sub-classing or via deserialization.

These methods can be arranged in such a way that a trusted thread (such as one of the event threads) may be chained into calling method A which calls method B which calls method C ... which calls method Z. As only trusted code will be on the calling stack, the privileged context of the trusted thread is maintained. If one finds a method Z that does something interesting, such as a parameterizable method invocation via reflection, that invocation will take place in a privileged context.

This is very bad because it means that security relies that there not exist harmful combinations of methods signatures; something that is not feasible to control, as opposed to the very controlled model of having a number of doPrivileged blocks which are known to be dangerous, but relatively easy to control.

ZDI-10-056/CVE-2010-0840 leverages this by creating a trusted chain as follows:

- instantiate a vuln.Link (subclass of java.beans.Expression) pointing the invoke params to class: java.lang.System, method setSecurityManager, args: null
- instantiate a new java.util.HashSet and put the vuln.Link (which is also a Map.Entry) in the Set
- instantiate an anonymous subclass of HashMap where the entrySet method returns the instance defined above
- instantiate a javax.swing.JList object, passing the HashMap instance defined above as the list contents
- add the JList on any visible component (such as the applet itself)

And here's what happens when the digital Rube Goldberg machine is set into motion:

- the GUI thread wants to draw the JList and calls JList.paint(Graphics)
- JList while drawing itself, calls toString on the list contents, including said anonymous subclass of HashMap
- HashMap inherits AbstractMap's toString method which calls entrySet().iterator() and iterates the resulting Set, calling getValue on each Entry
- one of the Entry objects returned by the implementation is a subclass of Statement, which implements the Entry interface's getValue() method with the Statement.getValue()
- Expression.getValue() calls Statement.invoke()
- Statement.invoke() has been parametrized to call System.setSecurityManager(null) via reflection

...and Java security gets switched off.

Safety First:
The vuln.Link class cannot be created with a normal Java compiler. Hopefully that'll keep the virus writers at bay, while the details enlighten the security community.

The Fix:
Based on my very brief analysis, Java 6 update fixes this problem by altering the Statement.invoke() to use the AccessControlContext captured at the moment of instantiation when it uses the reflection.

14 comments:

Matthias Kaiser said...

Awesome work, I'll try to reproduce the vuln.
Again, thanks for all the details you share with us on your blog and hope to hear more from you. Apperently your are the only one who is really looking at internal java security issues (non-native ones)! Thanks again!

Sami Koivu said...

Thanks Matthias! I appreciate the interest, and look forward to reading about your discoveries.

Anonymous said...

Absolutely a great finding. Chapeau for your Java Internals skills :)

Bye, Francesco `ascii` Ongaro, ush.it

05hi said...

Very nice job. I managed to get the exploit working and execute commands through Runtime.exec. How do you pass 'java.lang.System' as a target to Expression? I'm sure it's trivial but can't seem to get it to work. Java is not my first language;)
Again, HIGH quality work! Thanks.

Sami Koivu said...

05hi: As System.setSecurityManager is a static method you pass System.class as the target param.

The three params that I'm passing to Expression are defined thusly:

Object target = System.class;
String methodName = "setSecurityManager";
Object[] args = new Object[]{null};

Sami Koivu said...

Francesco: Thanks!

05hi said...

ahhh I even tried System.class before but passed a wrong argument by mistake, got method not found exception and wrongly assumed it was the target object part. Anyways, thanks Sami!

Also in the last 3 points of the 'what happens' part you switched Statement with Expression. e.g.
- Statement.getValue() calls Expression.invoke()

this should read
- Expression.getValue() calls Statement.invoke()

Sami Koivu said...

05hi: Thanks for the correction. You're absolutely correct. Fixed it.

Daira Hopwood said...

Good work! Why do you need a class that can't be compiled from Java code?

Based on my very brief analysis, Java 6 update fixes this problem by altering the Statement.invoke() to use the AccessControlContext captured at the moment of instantiation when it uses the reflection.

Sigh, fixing the particular instance rather than the underlying design problem with use of stack inspection (as always).

Sami Koivu said...

Thanks.

I need a class that can't be compiled, because the attack requires the creation of a class which extends java.beans.Expression and implements java.util.Map.Entry. Something like this:

public class Link extends Expression implements Map.Entry {
...
}

And the java compiler will not like some conflicts between the methods of the superclass and the interface:

Map.Entry:
Object setValue(Object value);
Object getValue();

Expression:
public Object getValue() throws Exception ...
public void setValue(Object value) {

The conflicts from the setValue's return type and getValue throws clause make it impossible to compile that class. However if you go ahead and generate a class, the runtime accepts it just fine.

Daira Hopwood said...

And the java compiler will not like some conflicts between the methods of the superclass and the interface...

Ah, that explains it.

It's documented that exception declarations are ignored by the VM, but I'm slightly surprised that the different return type is accepted. (IIRC, return types were originally novariant, but that was changed to support covariance at some point, so maybe that's why it's accepted.)

Sami Koivu said...

In this case, I believe the setValue methods are treated by the VM as two separate methods, because of the differing signature (=return type). I wrote some years ago about how some obfuscators create classes that overload fields, and methods with the same parameters but different return types.

Another point is that as the attack doesn't require calling the setValue method, it might never get verified anyway. My third vulnerability fixed in update 19 (that I didn't write about yet) deals with creating a class that doesn't have a valid constructor, but instances of which get deserialized without any problem.

NCR said...

i would like to know how did you discover this bug.

Hyman said...

This is awesome!