We're about to get another CVE to our name.
Let's talk a bit on how we found it. One of our customers commissioned a test of their infrastructure. On of their systems was running the Crashplan backup server from Code42, and we found a remote code execution possibility. As luck would have it (for our customer that is) a setting in their firewalls made it impossible to exploit it in their environment, but naturally we reported it to Code42. Their response was... well, not what we hoped...
Anyway, here's a short write-up of how we found it (and we usually shorthand Radically Open Security into 'ROS' when doing these writeups, so I'll continue that). And when I say "we" I really mean "Erik Bosman" since he's the pentester who did all the work on this one, and he deserves all the credit here.
During a port scan on crashplan.REDACTED.com, ROS found an open port with a service communicating using an unknown binary protocol. The service on TCP port 4282 seemed to be sending Java class names over the wire.
00000000 80 63 00 00 00 41 2d 31 38 37 38 32 7c 63 6f 6d |.c...A-18782|com| 00000010 2e 63 6f 64 65 34 32 2e 6d 65 73 73 61 67 69 6e |.code42.messagin| 00000020 67 2e 73 65 63 75 72 69 74 79 2e 53 65 63 75 72 |g.security.Secur| 00000030 69 74 79 50 72 6f 76 69 64 65 72 52 65 61 64 79 |ityProviderReady| 00000040 4d 65 73 73 61 67 65 b6 a2 00 00 00 22 01 00 bf |Message....."...| 00000050 a9 03 69 25 02 11 8e 7f aa f9 e9 88 14 98 a4 9c |..i%............| 00000060 0e 1c 30 61 73 fa e2 77 9d 10 88 a4 21 6c bb |..0as..w....!l.| 0000006f
Finding class names in a messaging protocol can be indicative of the use of (de)serialization. (De)serialization is the process of translating an object (or a group of objects) in memory to a stream of bytes and back. These objects can then be sent over network or stored on disk. Deserializing arbitrary objects from untrusted data is tricky and can often lead to remote code execution. The main problem is that allowing an untrusted party to create arbitrary objects on a system exposes a lot of normally unreachable code, greatly increasing the attack surface. As an example, the standard way of deserializing objects in Java, using ObjectInputStream can be exploited to gain code execution using publicly available code (Ysoserial: https://github.com/frohoff/ysoserial)
The software running on the host in question turned out to be the code42 backup server. Unfortunately, we failed to obtain a test license from code42, so the source code (for versions 5.3.4 and 5.4.0) was obtained using a download url from a presentation on Youtube and decompiled using CFR. This, however meant that we could not truly test the system locally (and reverse engineering the licence enforcement was way beyond the scope of the pentest.)
Our analysis revealed that code42 uses a number of different ways to serialize and deserialize objects. Objects implementing the IMessage interface, sent over port 4282 are deserialized using one of two methods:
- Legacy Messages
- A home-brew deserialization method for data objects implementing the IMessage interface
- Google Protocol Buffers
- A newer method, also employed to (de)serialize IMessage objects. It is up to the sender to decide which method is used.
However, some of these messages have custom deserialization implementations or they contain references to other objects which are not IMessages themselves. ROS found a number of (de)serialization implementations being used:
- com.code42.io.TinySerializer
- recursively deserializes objects with only primitive types. ROS did not find a way to exploit this deserializer.
- com.code42.io.C42WhitelistObjectInputStream
- A subclass of the vulnerable java.io.ObjectInputStream, however, this class overloads the resolveClass method to only allow deserialization of a select subset of objects, thwarting exploits generated using Ysoserial.
- com.code42.io.CompressUtilityWhitelist
- A decompression wrapper around C42WhitelistObjectInputStream
- java.io.ObjectInputStream
- Some code is still using the directly vulnerable java.io.ObjectInputStream, however, this code did not seem related to network communication and we did not find a way to ObjectInputStream directly on our own data.
While com.code42.io.C42WhitelistObjectInputStream limits the types of objects that can be loaded, it is not immediately clear that it can prevent exploitation alltogether.
First, while it is not possible to create instances of classes outside of the whitelist, due to a loophole, by creating a 'Proxy' class, which partially bypasses the overloaded resolveClass method, it is still possible to load any class, even non-serializable classes, but not to create an instance of it. The act of loading a class alone can result in the execution of code defined in static { ... } sections of classes. We created a program which tried to load all classes in the classpath used by Crashplan. This did yield some interesting results, such as (non-functional) Java GUI windows popping up, and a logger being started. But it did not give us any results which would allow us to exploit the program.
More importantly, the number of classes still allowed is quite large, the whitelist allows objects which match any of the following rules:
- Object is an array
- Object is serialiable and part of the com.code42 package or any of its sub-packages
- Object is serialiable and part of the com.backup42 package or any of its sub-packages
- Object is serialiable and part of the java.lang package or any of its sub-packages
- Object is serialiable and part of the java.util package or any of its sub-packages
- Object is serialiable and part of the com.google.common package or any of its sub-packages
- Object is serialiable and part of the com.google.inject.internal; package or any of its sub-packages
- Object is of the java.io.File class
- Object is of the sun.util.calendar.ZoneInfo class
Since all publicly available exploit code uses classes outside of this whitelist, we needed to look for new ways of creating an exploit payload in this still sizeable list of allowed classes. Given that this would potentially lead to remote code execution on the backup server, a system which presumably gets sent sensitive organization data from lots of different sources, we put a considerable amount of time in trying to find an exploit payload which would work. In the end, we were unable to come up with a useful exploit chain. This is not to say that there might not be one in the future. The whitelist allows for entire subtrees of packages to be deserialized, new classes may added to packages, for example when a new version of Java comes out, or when google updates their java library.
Another avenue that looked interesting, but which we were unable to make work was using the java.lang.invoke.SerializedLambda class. This class implements the recently added lambda expression support in Java. What is special about this is that any lambda expression in the codebase gets serialized as this class and since this class is whitelisted, this means we may be able to deserialize any lambda expression. However, we did not find a suitable expression in the codebase to further our goal.
== Pwned in an instance(?) ==
Being unable to find a suitable exploit for the com.code42.io.C42WhitelistObjectInputStream red herring, we turned our attention to the custom serialization that is at the heart of the message parser that is listening on port 4282.
To save network traffic sending class names, new classes are registered with com.code42.messaging.MessageFactory once, and referred to with a numeric identifier after. The ClassMessage message, when deserialized, uses the classloader to load the class specified in the message, without checking whether this class actually is a valid message, and it isn't bound by any whitelist like C42WhitelistObjectInputStream. Then, when the class is loaded, we can send a message, which is of this class.
com.code42.messaging.MessageFactory will then try to instantiate this message with the following code:
try { message = (IMessage)type.newInstance(); } catch (Exception e) { log.error("Unable to instantiate new instance! Missing default constructor? - msgUid={}, type={}", shortUid, type); }
If type does not implement the IMessage interface, this will ofcourse fail, but not before creating an instance of an arbitrary object with a default constructor. To investigate if this would be a problem, ROS tried to instantiate all objects in the classpath (including the jars that are shipped with crashplan), monitoring network connections, to see if any of them resulted in useful behaviour. In an overnight run of this test, we found that org.apache.commons.ssl.rmi.DateRMI creates a listener socket (on an arbitrary TCP port) upon instantiation. Some further research yields that this listener socket in fact is a Java Remote Method Invocation server. This server in turn is vulnerable to the same deserialization attack we tried to attack before, but without a whitelist. Testing locally, we were able to gain remote code execution using a slightly modified version of Ysoserial on the newly created socket, but due to the unused ports on crashplan.REDACTED.com being filtered, we were unable to exploit this on the REDACTED infrastructure.
However, we still deemed this to be a security risk. If the firewall is ever misconfigured or temporarily turned off, or if an attacker can get behind it, this would lead to arbitrary code execution. It may not be the easiest one to fix, since it requires some serious rethinking of the serialization methods used, but that should not be a reason to shift the responsibility to the customer. Besides, the whitelist is so wide (perhaps call it a widelist instead?) that it's an accident waiting to happen.
Go Top