六、CommonsCollections6
为什么CommonsCollections1后直接看CommonsCollections6了?
因为据说CommonsCollections1和CommonsCollections6很像,趁热打铁,把6也学了
分析
代码分析
先看下代码
CommonsCollections6的代码很长啊
先看一部分
红框内的代码在CommonsCollections1内已经学过了,构造了一个LazyMap对象,只要调用它的get方法,就会触发利用链
继续看下面代码
TiedMapEntry是什么?
看下构造函数
把map和key保存了
再继续看代码
创建了一个HashSet,容量为1,添加了个"foo"
然后看下一部分代码
(这里的try catch应该是为了兼容不同版本的jdk)
这部分代码看起来很多,其实很简单,简写就是下面这样
map.map.table[0].key=entry;
return map;
只不过因为有些成员变量是private类型,不能直接访问,这里用了反射的方式修改
继续分析下这些变量
HashSet的成员变量竟然有HashMap,看一下果然,成员变量有map
HashSet的add方法在添加新元素时,会把它设置为map的key,因为HashMap的key唯一嘛,所以HashSet将HashMap的key当做自己的元素,通过这种方式保证了HashSet没有重复元素(集合的特点之一)
再看下HashMap的table如图
table是Node类型数组,Node是HashMap的内部静态类
HashMap每添加一个新元素都会放在Node数组里
而Node类包含了map的hash,key,value等信息
好了,再回头看一下第二部分代码
map.map.table[0].key=entry;
return map;
这下理解了把,map是集合,内部通过字典的键来实现元素唯一的
那这句话
map.map.table[0].key=entry;
意思就是把map对象内的第一个元素值修改为entry呗
等下这是什么操作,
先再map内添加元素"foo",再把这个元素改为entry,那为什么不直接再map内添加entry?
调试分析反序列化过程
如图,先写个代码调试下
由于经过上面分析,我们已经知道了,最终肯定是由于调用了LazyMap的get方法,造成命令执行
那就直接再LazyMap的get方法上加个断点
启动,调试
果然是这里触发
看一下调用链
问题的根源在这里,HashSet有自己的readObject方法,它和HashMap同理,先创建个空的HashSet,再把元素一个个put进去
可以看见,这里put的元素正是之前构造的 TiedMapEntry对象
看下put时发生了什么
获取TiedMapEntry对象的hash
调用TiedMapEntry对象自己的hashCode方法
如图,调用了map的get方法,回想一下TiedMapEntry的构造函数,这个map是什么?
是LazyMap对象
调用了get方法,所以导致触发了命令执行
ok,至此,这条利用链原理弄清了
总结
这条利用链可以分为三部分
第一部分是构造LazyMap对象,调用get方法即可触发rce
第二部分是构造TiedMapEntry对象,TiedMapEntry的hashCode方法是同时调用key和map里key对应的value的hashCode方法,然后进行亦或,而在获取map的value时,调用了map的get方法
第三部分是构造HashSet对象,HashSet在反序列化时,调用map.put,put方法会调用key的hashCode方法
所以反序列化时->HashSet的put->TiedMapEntry的hashCode->LazyMap的get
疑问
为什么要先再HashSet对象内放个"foo",然后通过这么多反射,将这个元素修改为entry,直接添加entry不行吗?
测试下看看,write时弹计算器,read时没弹
问题出在哪了?
调试下看看
跟踪反序列化流程
到了调用LazyMap的get方法的时候,没通过if条件
因为map里已经有foo这个键了
为什么?创建LazyMap对象时明明是空的
回想一下代码,哪里最可能在map里添加了键值
map.add(entry);
只有这里嫌疑最大
看下add方法
在HashSet的map里将TiedMapEntry对象设置为key
这里没问题,没对TiedMapEntry对象做出修改
再看下put方法
这里就有问题了
回想一下,上面的总结,对TiedMapEntry对象计算hash,就会调用TiedMapEntry对象的hashCode方法,然后就会调用LazyMap的get方法
在上一篇学习LazyMap的时候分析过,它的get方法
键不存在,则会新建一个键值对
所以,问题就在这里
HashSet调用add添加的元素,会在LazyMap里添加一个和这个元素相同的key
则下次调用LazyMap的get方法时,不会创建新的键值对,也就不会调用Transformer链,不会造成rce
所以在构造payload时,不能使用HashSet的add方法添加构造好的map对象,要用反射修改
ok,至此,CommonCollection6结束了