JDK7u21反序列化分析
前言
继续学习反序列化,这个链子正好用到了前面fastjson反序列化的TemplatesImpl
类,这里就不详细讲解该类的具体用法,有兴趣的请看我上一篇fastjson反序列化文章和cc2分析文章
环境复现
这里直接引用了ysoserial.jar
这个包,使用其自带的paylaod
|
|
漏洞分析
首先来看一下LinkedHashSet
这个类,它继承了HashSet
并实现了Serializable
可以进行反序列化,其readObject()
方法在父类HashSet
中
|
|
在其方法内会对map进行put操作,其中PRESENT
是new了一个Object空对象
|
|
进入到java.util.HashMap#put
中,在if判断语句内有这样一个关键的点key.equals(k)
,为什么它是关键呢?我们知道在动态代理中有这么一个知识点:动态代理对象每执行一个方法时,都会被转发到实现InvocationHandler接口类的invoke方法。也就是说在反序列化时我们将一个动态代理的对象放入到map中,在该方法中执行key.equals(k)
时就会触发代理类的invoke
方法
在AnnotationInvocationHandler
类的invoke
方法中,会对动态代理对象执行的方法名字进行判断,当为equals
时会进入到equalsImpl
中
在equalsImpl
中会对传进来的var1类中的方法进行遍历,并通过var8 = var5.invoke(var1)
去调用其方法,可以看到当我们传进去的类为TemplatesImpl
时,会遍历出getOutputProperties
方法并且去调用该方法,而我们知道调用了getOutputProperties
就会最后触发TemplatesImpl.newTransformer()
然后进行实例化触发恶意操作
这样整条链子就齐了,通过javassist构造一个恶意类,然后对LinkedHashSet进行反序列化触发,其中肯定是需要满足一些条件的,这里就不提TemplatesImpl要满足的点,来看一下在触发key.equals(k)
时需要满足的点,这是整个链子最厉害的点
先来看一下第一次进行map.put()
传进去的值是什么,可以看见第一次是传进去的TemplatesImpl
类
然后获取key
的hash,在通过indexFor()
获取hash的索引值,然后在for循环中根据索引值进行判断,其中table
是一个Entry数组,用来存放我们传进来的键值对,为了后续对新的value和老的value进行判断,其中还有这么一个条件e != null
,但是这是我们第一次传进来的键值对,Entry
本身就是空的,因此直接跳过循环进行自增,并放入到Entry
中
|
|
第二次进行map.put()
传进去的值是我们封装的AnnotationInvocationHandler
对象
这一次就会触发key.equals(k)
,但是这里我们需要满足两个条件,才会去触发key.equals(k)
|
|
- e.hash == hash 为true
- (k = e.key) == key 为false
其中e.hash == hash
要为true就是说从Entry
中提取的hash要和传进来的key的hash要一致,而我们知道for循环是为了更新相同key的value值(其中相同key的判断条件为hash值相等),可是我们传进来的两个key根本就不相同分别是TemplatesImpl
和封装的AnnotationInvocationHandler
代理对象,如何让其hash相等呢?这里用到了一个新的知识点hash碰撞
hash碰撞就是指两个不同的字符串计算得到的hash值相同
继续看,在第二次进行map.put()
时,进行hash值的计算,会通过k.hashCode()
计算hash值,而此时k为动态代理对象,它会触发执行AnnotationInvocationHandler
的invoke
方法
其中进行判断,如果动态代理对象执行的方法名为hashCode
时,进入到hashCodeImpl()
中
在hashCodeImpl()
中,通过迭代器对memberValues
对象进行遍历,其中存放着我们传进去的键值对
来看下面这段代码,其中通过异或来获取hash值赋值给var1,其中127 * ((String)var3.getKey()).hashCode()
对传进来的key值进行获取hash,此时key为f5a5a608
,而它的hash值为0,然后memberValueHashCode(var3.getValue())
是对传进来的value值进行获取hash,其中如果值不为数组的话返回hash
|
|
我们知道0和任何数字进行异或,得到的结果都为被异或数的本身,而此时value为TemplatesImpl
,也就是说在第二次进行map.put()
时,进行hash值的计算得到的hash值为TemplatesImpl
的hash和第一次传进来的一样,这样会在java.util.HashMap#put
中符合for循环的判断,为相同hash的key进行value的替换,此时会进入到if条件语句中
|
|
此时e.hash == hash
为true,而(k = e.key) == key
必定为false因为是比较两次传进来的key是否相等,而我们传进来的两个key分别是TemplatesImpl
和封装的AnnotationInvocationHandler
代理对象,因此这个条件也满足,然后就会触发key.equals(k)
,执行恶意操作