Introduction













Last month, Oracle released the quarterly security patch update, which fixed dozens of vulnerabilities. One of these vulnerabilities, CVE-2013-5842, has aroused my interest. This vulnerability is caused by a race condition in java object serialization/deserialization and could lead to potential remote code execution. I took some time in analyzing this vulnerability as well as trying to write a POC exploit for it. Unfortunately, due to the nature of this vulnerability, it is very difficult to develop a reliable exploit for it. Just like many other race condition vulnerabilities, the exploit of this vulnerability relies on the time window and CPU execution sequence, which cannot be fully controlled by an attacker.

However, since this is the first time that I deal with a java race condition vulnerability, I still want to write something about my analysis, and also give out a POC exploit in a “Simplified Environment” (I will explain this later).





The Vulnerability





This vulnerability is caused by a race condition in JRE library code which deals with the object serialization/ deserialization. The related source files are and java/io/ObjectInputStream.java and java/io/ObjectOutputStream.java.

Let’s take ObjectoutputStream as an example. This class is used to convert a java object to the corresponding serial data. Consider the following code:





ObjectOutputStream oos = new ObjectOutputStream(…);

ClassA objA = new ClassA(…);

oos.writeObject(objA);





We first creates an instance of ClassA, and then write (convert it to serial data) the object by calling ObjectOutputStream.writeObject. When writing a java object, we need to write all of the fields (including primitives values object values) within the object. To write the fields correctly, we need to know about the structure of the class (e.g. each field’s type, offset in the object …). Such information is provided by a java class named ObjectStreamClass(java/io/ObjectStreamClass.java). For example, if we have a class named “ClassA” defined by the following code:

public class ClassA implements Serializable {

int field_1;

int field_2;

String obj_1;

}





With 32-bit JRE, the memory layout of an instance of ClassA will be:

-----------------------------------------

Object Header (8 bytes)

----------------------------

field_1 (4 bytes)

----------------------------

field_2 (4 bytes)

----------------------------

obj_1 (4 bytes, pointer to string)

----------------------------

And the ObjectStreamClass of ClassA contains the following information (partial):





ObjectStreamClass of ClassA:

filed_1: type: “I”(int), offset in object: 8

filed_2: type: “I”(int), offset in object: 12

obj_1: type: “O”(object), offset in object: 16





With the help of ObjectStreamClass, we are able to write all fields of a java object using the correct offset. The “defaultWriteFields” method in ObjectOutputStream contains the default logic to write an object’s fields, it takes two parameters, the first parameter is the object to be written, and the second parameter is the corresponded ObjectStreamClass of the object:





private void defaultWriteFields(Object obj, ObjectStreamClass desc)





And what will happen if the two parameters of the method do not match? For example, the first parameter is an instance of ClassA, and the second parameter is the ObjectStreamClass of ClassB, while ClassA and ClassB have different memory layouts? Definitely it will cause JVM errors, and it is exactly the case of the vulnerability CVE-2013-5842. Let’s have a look at the code that call defaultWriteFields:

public void defaultWriteObject() throws IOException {

if ( curContext == null ) {

throw new NotActiveException("not in call to writeObject");

}

Object curObj = curContext.getObj();

ObjectStreamClass curDesc = curContext.getDesc();

bout.setBlockDataMode(false);

defaultWriteFields(curObj, curDesc);

bout.setBlockDataMode(true);

}





As we can see, the two parameters for the defaultWriteFields are retrieved from a variable named curContext, which is an instance of SerialCallbackContext. The curContext is a member object of ObjectOutputStream, it keeps the current object to be written, and the ObjectStreamClass related to the object. When curContext is initialized or changed, it is greatened that the desc matches the object, using the following constructor:







public SerialCallbackContext(Object obj, ObjectStreamClass desc)





However, in a multi-threaded environment, the above code in defaultWriteObject has a problem:

In a multi-threaded environment, an ObjectOutputStream can be used in different threads, when a thread (A) calls writeObject on an ObjectOutputStream , enters defaultWriteObject, and after executing the following code:





Object curObj = curContext.getObj();







At this point, a context-switch may happen, and the current thread (A) will be suspended, another thread (B) could get executed. If thread(B) calls writeObject on the same ObjectOutputStream, it can modify the value of curContext. Then when the thread (A) is resumed, it continues to execute the following:







ObjectStreamClass curDesc = curContext.getDesc();





Since the value of curContext has already been changed, it could get a ObjectStreamClass which does not match the curObj to be written, which will trig the vulnerability.

The following sequence diagram illustrates such condition:

















Write the Exploit





Now we have a vulnerability with which we can serialize an instance of Class A, using the layout info of Class B. Are there any cool things we can do with it? Yes, absolutely.





Information Leak

With this vulnerability, one of the things we can do is to read memory area beyond a java object. Consider the following two classes:

public class ClassA implements Serializable {

int field_1;

}

public class ClassB implements Serializable {

int field_1;

int field_2;

int field_3;

}

On a 32-bit JRE, an instance of ClassA occupies 12 bytes in memory (8 bytes header + (1) * (4 bytes)), while an instance of ClassB occupies 20 bytes in memory (8 bytes header + 3 * (4 bytes)).

If we make a call to defaultWriteFields like this:





defaultWriteFields(obj(ClassA), desc(ClassB))





When the function tries to serialize all the primitives of the ClassA object, it looks at the structure info of Class B, thus tries to read 12 bytes (3 integers) from the Class A object. Since a Class A object only have 4 bytes primitive values (1 integer), it will read 8 more bytes beyond the ClassA object’s memory.





Fake an object pointer with arbitrary value

Another thing we can do with this vulnerability is that we can set an arbitrary value (e.g. 0x41414141), and cheat the JVM to consider it as the pointer of a java object. We will explain how the magic works, consider the following two java classes:





public class ClassA implements Serializable {

int field_1 = 0x41414141;

}

public class ClassB implements Serializable {

string s_1;

}





If we make a call to defaultWriteFields like this:





defaultWriteFields(obj(ClassA), desc(ClassB))





Since the function takes the layout info of ClassB as the second parameter, it will try to read the string object “s_1” from a ClassA object. The offset of “s_1” in ClassB is 8, which is the same as the offset of “field_1” in class A. Finally it will call Unsafe.getObject (sun/misc/Unsafe.java) to read the value of s_1, where the parameter offset is set to 8.





public native Object getObject(Object o, long offset);





The Unsafe.getObject is a native method which will directly read a pointer (4 bytes in 32-bit Java) start from the specified offset in the object. Because the actual object to be read is a ClassA object, the getObject function will finally read the value of ClassA.field_1 (whose value is 0x41414141 here) and return it as a java object pointer.





Combine them together

With the above functions, it’s easy to combine them together to work out a POC exploit.

First we use the information leak approach to leak a pointer of a java object, then we fake a java object pointer and let it pointer into the middle of the leaked object. By setting the field values of the leaked java object to valid object head values, we can cheat the JVM to believe the fake object pointer is a valid java object pointer of a certain class. Then we can manipulate the fake object pointer as a normal java object, so that we can read/write a small memory area after the fake object pointer. By overwriting a java int array’s length field and using other tricks (you may check my slides in SyScan360: http://www.slideshare.net/xiong120/2013-syscan360-yukichensyscan360exploit -your-java-native-vulnerabilities-on-win7jre7-in-one-minute ), we can successfully disable the security manager and escape from the java sandbox.









POC Code and How to Test









The POC code we provided here only works on 32-bit Java, and when JRE 7 version <= 7u40.You may get fully POC code and binary from https://github.com/guhe120/CVE-2013-5842

As mentioned at the beginning of this blog, it’s very difficult to work out a reliable exploit for this race condition vulnerability. To make the POC and test easier, we will use some “trick” to make the race condition happen every time. What we do here is to modify the code of ObjectOutputStream.java and insert a sleep between the two problematic lines:





System.out.println( "Sleep in defaultWriteObject" );

try {Thread.sleep(5000);} catch (Exception e) {}

System.out.println( "Continue in defaultWriteObject" );





The modified ObjectOutputStream.class is published together with the POC code, under jre_dbg/java/io, when you test the POC, you should use this modified version of ObjectOutputStream.class.





How to Test

Suppose you download all the POC files and put them under G:\cve20135842, then you can test it like this:





cd G:\cve20135842\bin

java -Xbootclasspath/p:G:\cve20135842\jre_dbg -cp . TestDeserialize





The -Xbootclasspath option tells the JVM to use our version of ObjectOutputStream.class instead of the default installed class. Upon successfully exploit, you may see a calculator pops up and some log message telling us that we have succeeded in overwrite the java int array’s length field, as following:





arr_.length = 2147483647

Security Manager = null