Skip to content

Serialization Must Die: Act 2: XStream (Jenkins CVE-2016-0792)

    
Serialization Must Die: Act 2: XStream (Jenkins CVE-2016-0792)

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

public Object unmarshal(...) {
   Map map = (Map) createCollection(context.getRequiredType());
   populateMap(reader, context, map);
   return map;
}

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:


protected void populateMap(..., Map map) {
  while (reader.hasMoreChildren()) {
    reader.moveDown();

    reader.moveDown();
    Object key = readItem(reader, context, map);
    reader.moveUp();

    reader.moveDown();
    Object value = readItem(reader, context, map);
    reader.moveUp();

    map.put(key, value);

    reader.moveUp();
  }
}

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:

public int hashCode() {
  Object method = getProperties().get("hashCode");
  if (method != null && method instanceof Closure) {
    // invoke overridden hashCode closure method
    Closure closure = (Closure) method;
    closure.setDelegate(this);
    Integer ret = (Integer) closure.call();
    return ret.intValue();
  } else {
    return super.hashCode();
  }
}

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:

  1. MapConverter#populateMap() calls HashMap#put()
  2. HashMap#put() calls Expando#hashCode()
  3. Expando#hashCode() calls MethodClosure#call()
  4. MethodClosure#call() calls MethodClosure#doCall()
  5. MethodClosure#doCall() calls InvokerHelper#invokeMethod()
  6. InvokerHelper#invokeMethod() calls ProcessBuilder#start()

And, our calculator appears!

Putting It All Together

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

Schedule Demo

 

Arshan Dabirsiaghi, Co-Founder, Chief Scientist

Arshan Dabirsiaghi, Co-Founder, Chief Scientist

Arshan is an accomplished security researcher with 10+ years of experience advising large organizations about application security. Arshan has released popular application security tools, including AntiSamy and JavaSnoop.