序言
环境要求:CommonsCollections: 3.1-3.21 , java版本暂无要求
在cc1中是通过AnnotationInvocationHandler#invoke
来触发对恶意代理handler调用其invoke
方法从而触发LazyMap#get
方法。
而cc7中更加的直接,通过AbstractMap#equals
来触发对LazyMap#get
方法的。
分析
CC7中需要用到Hashtable
和AbstractMap
在AbstractMap
的equals
方法中,对m进行了get
操作,如果能使它为LazyMap
,就OK了
于是寻找能调用equals并可行的地方,这里采用的是Hashtable#reconstitutionPut
key可控,即代表m可控
HashMap继承AbstractMap
那为什么选中这个reconstitutionPut
呢
是因为Hashtable
的readObject()
中正好调用了他
看writeObject()如何序列化的
这里的entryStack.key就是通过put添加的值
那么流程也就简单了:
Hashtable#readObject -> Hashtable#reconstitutionPut -> AbstractMap#equals -> Lazymap#get
构造
前半段老样子就不说了
主要看后半段
这里有几个问题:
1 | 1.为什么要生成两个lazymap,put两次 |
1.为什么要生成两个lazymap,put两次
按理说我们只需要将Lazymap作为key,put进Hashtable即可。那么这里为什么要put两次呢?
调试发现,第一次Put将key和value存入tab,但是第一次一开始table[index]是null,不会进入for循环中
无法进入循环,也就无法调用equals方法
而在接下来才会将传入的值存入tab中。
所以说第一次Put的作用就是注册tab的内容,让第二次put的时候能成功进入到for循环,触发equals()。(备胎?
2.为什么偏偏设置yy和zZ?
既然put了两次,那么这里hash,index自然也计算两次,已知第一次put是为了注册tab,而如果第二次计算的index不等于第一次的话,就导致tab取不到值,e为Null,不进入for循环。
而这里相当于一个trick或者说bug,"yy"
和"zZ"
的计算结果一样
那为什么两次不传入同一个key呢?
可以看到在readObject()中,我们要保证两次Put,第一次注册tab,第二次触发equals(),那么就要保证这里成功循环两次。
如果两次是一同一个的值的话,elements就为1,无法满足条件。
3.为什么要反射设置一遍chainedTransformer的值?
原因很简单,在hashtable#put
中也会导致调用equals的操作,所以这么做的目的就是防止在生成payload的时候本地触发RCE。
所以才一开始给chain链一个空数组,后面再通过反射赋值
4.为什么最后进行了remove(“yy”)
在AbstractMap#equals中
的size()
结果为1,但是从Hashtable传递过来的m.size()
却是2,二者不等便返回false
在将yy删除后,二者便相等了
那为什么偏偏删除yy呢?
从上面分析已知在第一次put(lazymap1)的时候,put方法也会调用一次equals()
然后跟进到Lazymap#get
因为是lazymap1,所以这里的key是yy。
(ChainedTransformer由于构造时传入空数组,所以这里是空)
接下来会 put("yy","yy")
可以看到多了一个yy键对,所以最终结果就是增加了一个yy键值
这样一来,上面提到的if条件也不满足
remove("yy")
后:
接下来就能继续往下走,调用Lazymap#get
接下来大家都懂,就不说了(非空是因为通过反射赋值)
尾声
CC链系列暂时就分析到这里吧,一系列分析下来,自己慢慢调试,过程中时刻思考这里为什么会这样,为什么要这样设置,从最开始的寸步难行,到后面的有自己的思考,的确学到很多。