前言
上一篇分析了ysoserial
中的CC1链,这篇主要分析CC6这条链。
分析之前,先来看看CC6这条链是如何诞生的呢?
我们都知道,在CC1
这条链中,AnnotationInvocationHandler
在执行反序列化时,触发的点是memberValues.entrySet()
。
这是在JDK 8u70
之前,我们来看看JDK 8u70
之后的AnnotationInvocationHandler
类,这里用的是JDK 8u301
。
可以看到,已经将memberValues.entrySet()
换成了其他的,在JDK 8u71
更高版本中,就无法继续使用这种方式进行利用了。
那有没有一条不受JDK版本限制的链条呢?
答案肯定是有的,也就是即将准备分析的Common-Collections6
利用链。
利用链分析
首先,还是先来看看ysoserial
的cc6
利用链
看起来 还是挺长一串的,但是可以看到,命令执行的方式依旧没变,还是使用了LazyMap
的get
方法。前面的构造方式已经在《Java反序列化Common-Collections1利用链分析》中说过了,直接对后面的进行分析。
它使用了TiedMapEntry
的getValue
来触发LazyMap
的get
方法。
接下来看看哪里调用了getValue
方法
在TiedMapEntry
的hashCode
方法中调用了getValue
方法。
在HashMap
的hash
方法中找到调用hashCode
通过查找引用,可以直接在HashMap
的readObject
方法中找到
但是ysoserial
中并不是使用的HashMap
,而是使用的HashSet
,其实都是一样的,HashSet
的readObject
方法只不过是间接地调用了hashCode
。
在HashSet
的readObject
中
put中还是调用了hash
接下来对利用进行梳理,首先
AnnotationInvocationHandler->readObject
AnnotationInvocationHandler->invoke
LazyMap->get
跟CC1中的一致,原先的AnnotationInvocationHandler
类换成了TiedMapEntry
的getValue
来触发,然后在TiedMapEntry
的hashCode
调用了getValue
,而后HashMap
在readObject
中直接调用了hash
方法,最后hash
方法中调用了hashCode
方法。
HashSet
在readObject
中则是通过map.put
方法间接调用了hash
方法。
注意:这里还有一个细节问题,在创建TiedMapEntry
对象的时候,我们需要传递一个map
和一个key
这里的map就为LazyMap
的实例,而key,我们通常会随便传递一个。
如果我们直接执行,是不会弹出计算器的,这是为什么呢?
打个断点,调试一下就知道了。
在LazyMap
的get
map.containsKey(key)
是用来判断map
中是否存在key
这个值,如果不存在,会将key
存放到map中,如果存在,就不会执行transform
。
为什么会在序列化之前执行到这个方法里面去呢?
因为需要通过HashSet
来触发TiedMapEntry
的hashCode
,所以我们要将TiedMapEntry
通过add
方法添加到HashSet
中去, 那么在add
方法中,也会调用put
方法,所以也会间接调用到hash
,提前触发命令执行。
那么怎么避免呢?
其实很简单,只需要在序列化前,用map.remove(key)
将HashMap
对象中的key
移除就好了。
利用链构造
得到一条利用链
1 | HashSet->readObject |
1 | package com.example; |
文章逻辑可能写得有点混乱,希望大佬们多多指教。