NOTE: Before you begin reading, you may want to visit the first article in this series: Serialization Must Die: Act 1: Kryo. That piece frames some of the discussion for this current blog.
XStream is a popular deserialization library. It’s used directly by many popular apps, like the build tool, Jenkins. It’s also used by other popular libraries, like Spring and Struts 2 for unmarshalling XML input into objects.
Awesome guys @diniscruz and @pwntester released an exploit against XStream with a great write up in December of 2013. Their exploit’s primary “gadget” was java.beans.EventHandler, which is available in the JRE -- no need for any special types to be available to exploit the victim. XStream put in a special check to prevent this gadget from working, thus committing themselves to gadget whack-a-mole.
Another Day, Another Mole
The following new, possibly pre-authentication exploit against Jenkins (CVE-2016-0792) works because Groovy is on the classpath. There are probably a million other apps that use XStream and have Groovy on the classpath. I put almost no effort into trying to find this vulnerable pattern in other open source applications -- this Jenkins CVE is just one of many.
Here’s the final exploit, with some of the more influential bits highlighted. We’ll explain this piece by piece afterwards:
If you supply the above code to any endpoint which parses the XML using XStream, calculator will pop, as shown in the below attack against Jenkins:
What? Why? Who Let This Happen?
Let’s break down the exploit.
The root node in the exploit XML is <map>. Let’s see what XStream does when it’s trying to reconstitute a map in MapConverter.java.
Ok, so the default type for <map> is java.util.HashMap (although that’s not shown here.) XStream creates one that it will populate with the entries the user supplies. The entries are assumed to be the direct child elements of <map>, in order, like this:
Let’s see how this plays out in the XStream code:
As highlighted, the user controlled key and value variables read in, and then put into the HashMap. So, let’s look at the HashMap#put() method:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
...
}
Ok, so as part of the Map#put() call, XStream is indirectly causing key.hashCode() to be called. This is our entry point. If we can repurpose the hashCode() method of some type to do something malicious, we’ll win.
Hocus Pocus, Alakazam, Expando!
Now we introduce the type we’ll abuse: groovy.util.Expando. It has an intriguing hashCode() implementation:
The above code boils down to this: if the Expando has a Closure that’s supposed to figure out the hash code, the Expando will call that Closure and return its output. All we have to do is supply a Closure to use!
Since Closure is abstract, what subclass should we give it? This is a no brainer: We’ll give it a MethodClosure! A MethodClosure is a wrapper that calls an arbitrary class and method name. We’ll create a method closure that calls start() on a java.lang.ProcessBuilder to pop a calculator. Now the call chain is like this:
With the big picture in order, we can take another look at the exploit XML, more commented:
Can We Exploit Kryo With the Same Gadget?
No. For Kryo to deserialize an Object (under default settings), its type must have a zero-argument constructor. The groovy.runtime.MethodClosure class does not have such a constructor.
XStream has a different default object instantiation strategy. It uses sun.misc.Unsafe#allocateInstance() method to instantiate objects without calling their constructor. This is a JVM-specific technique -- it’s not guaranteed to work everywhere, but in practice, it probably will.
This is cool because it prevents side effects in constructors. And a hidden part of constructors (because it happens in native land) is the registering of newly created objects to be finalized. The constructors and finalize() methods were what caused all the problems in Kryo, but neither will happen with XStream-created objects.
There is no secure answer for object instantiation strategy when it comes to serialization -- just tradeoffs.
Summary
It’s gadget whack-a-mole right now, until folks start whitelisting the types they want to allow. There are more interesting gadgets out there to find.
Vendor Response
The Jenkins team was incredibly responsive, despite being under attack by spammers at the time of reporting!
02/03/2016: Vendor notified
02/12/2016: New detection and prevention rules added to Contrast
02/16/2016: Rules released to customers
02/24/2016: Vendor releases fix
02/24/2016: Public disclosure
For Contrast SEcurity Customers
Here’s the good news: Contrast Security customers have been getting alerts about vulnerable XStream usage and protected against attacks since February 16, 2016.
Schedule a Demo today and see how Contrast Security can notify you of vulnerable Xstream usage within your Apps
Get the latest content from Contrast directly to your mailbox. By subscribing, you will stay up to date with all the latest and greatest from Contrast.