前言
上一篇演示了如何构造dns查询的序列化payload
这个利用链是在网上找的,其实java还有很多被挖掘出的利用链
ysoserial库是别人写好的库,包含了至今公开的所有反序列化利用链
可以通过看它的源码,来学习java反序列化利用链
上篇文章的案例,构造dns查询payload,在ysoserial库中也有写好的,更完善的利用链
这篇来看下ysoserial库是如何实现URLDNS利用链的
正版URLDNS
// 先导入URLDNS类
import ysoserial.payloads.URLDNS;
看一下ysoserial的URLDNS如何构造payload
步骤基本上和上一篇我们分析的一样
注意有下面两个区别
在上一篇,构造URL对象时,为了防止在构造payload时发出dns请求,先将hashCode设为非-1的值,然后添加进HashMap,再把hashCode改为-1
而ysoserial写了一个空的URLStreamHandler类用来实例化URLStreamHandler接口
这样在构造payload时触发的就是SilentURLStreamHandler里的空方法(SilentURLStreamHandler继承自URLStreamHandler,实例化后,赋值给了URLStreamHandler对象,所以在反序列化时,还是URLStreamHandler类型)
而反序列化时,调用的仍是URL对象自动生成的handler(URLStreamHandler类型),触发dns查询ysoserial还写了一个Reflections类
Reflections类的方法可以绕过作用域的限制(怎么绕过在反射篇里已经分析过了),无视作用域修改、获取变量值,调用方法、构造函数等
URLDNS作为反序列化学习的第一站,很好入门,
后面会学习ysoserial库的CommonCollections系列
万事开头难,所以在正式学习CommonCollections系列前,先学习下p神简化版的CommonCollections1,做一下下一篇正版CommonCollections1的预习
简化版CommonCollections1
先看下下面代码
// 代码来自p神的知识星球
public class Collections {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
outerMap.put("test", "xxxx");
}
}
运行就会弹出计算器
什么时候触发的命令执行呢?去掉最后一行outerMap.put("test", "xxxx");
试试
没有计算器弹出,看来是最后一步outerMap.put触发了命令执行
调试下看看,发现执行这里弹出了计算器
跟进
再跟进this.valueTransformer.transform(object)
遍历iTransformers,用transform方法处理object
第一个transform返回了Runtime对象
第二个transform通过反射,执行了exec函数,导致命令执行
现在再回来看一下代码,
第一步先创建了一个Transformer对象数组,三个元素分别是Runtime,exec,calc
Transformer是做什么的?
Transformer是接口,只声明了一个函数Object transform(Object var1);
ConstantTransformer和InvokerTransformer是实现了Transformer接口的类
ConstantTransformer是什么?
看下它的构造方法和transform方法
相当于构造时给它传入什么对象,调用transform就能获得什么对象
InvokerTransformer是什么?
看下它的构造方法和transform方法
先看构造方法
从参数名上来看,构造函数是:函数名、函数参数类型、函数参数
再看transform方法
参数是一个对象,然后直接通过反射调用对象的函数,这个函数是调用构造方法时指定的
这就解释得通了,在outerMap.put("test", "xxxx");时,把value值扔进transformers数组的第一个元素的transform方法,再把返回值扔给第二个元素的transform方法
第一个元素返回值是Runtime对象,第二个元素transform方法调用了Runtime的exec导致命令执行
所以,只要调用put方法,就会导致命令执行
再看这部分代码
跟进TransformedMap.decorate,看看做了什么
参数为:Map、keyTransformer、valueTransformer
很容易联想到,这个方法是对map进行处理的,Transformer是转换工具的意思
TransformedMap.decorate应该是对map的key和value进行处理,key使用keyTransformer处理,value使用valueTransformer处理
猜测使用场景是这样:
map为{"1":1,"2":2,"3":3}
使用TransformedMap.decorate,对map处理,假设valueTransformer作用是计算传入参数的平方,然后返回,传入keyTransformer为null,即不对key处理
则,最终得到的处理后的map是{"1":1,"2":4,"3":9}
Map是接口,TransformedMap.decorate返回的对象,实现了put方法
如图,每添加一个键值对,都要经过transform处理
那ChainedTransformer是做什么的?
看一下它的源码
构造函数是Transformer数组
看代码是将transformer数组串联起来了,接力处理数据
相当于工厂的流水线,例如扔过来一个水瓶,第一个Transformer加水,第二个Transformer拧瓶盖,第三个Transformer贴包装,然后返回成品矿泉水
现在在回头看一下完整的代码流程
流程分析
创建两个Transformer,第一个返回一个Runtime对象,第二个通过反射调用对象的指定方法
使用ChainedTransformer,把1中的两个Transformer串联起来,生成新的Transformer返回
使用TransformedMap,构造一个map(这个map的put方法是TransformedMap实现的,每次调用put都会使value经过2中构造好的Transformer)
调用map.put方法,value值传入1中的第一个Transformer,调用了串联的transform方法:获得Runtime对象--->Runtime对象反射调用Runtime对象的exec方法,导致命令执行
这就是简化版的CommonsCollections1了,看懂了代码,其实很简单
思考
但是,这是代码执行时调用put方法触发rce,怎么构造恶意反序列化数据才能让它在反序列化时导致命令执行?
上一篇中经过分析,HashMap对象在反序列化的过程是,先创建个空HashMap对象,然后再把键值对put进去,就会导致发出dns请求
本例中的outerMap对象是TransformedMap生成的
我在测试时,试着将outerMap序列化,结果
为什么?
因为outerMap是Map类型,Map类型没有继承Serializable接口,而上一篇用的是HashMap,继承了Serializable接口,
那把代码改一下
序列化成功,
但是反序列化时候没有命令执行成功,为什么?
根据上面案例,总结出,只要调用outerMap的put方法,就会导致命令执行,而HashMap在反序列化时会调用put方法(在上一篇分析过的)
所以反序列化map.bin就会造成命令执行?
错,"只要调用outerMap的put方法,就会导致命令执行",这里的put方法是由TransformedMap实现的,而反序列化时调用的是HashMap的put方法,HashMap的put方法不会调用Transformer,也就不会命令执行
那怎么构造?
看下正版CommonsCollections1是怎么实现的吧
下篇讲