之前听别人讲解反序列化的漏洞听的晕乎乎的,刚脆就趁着周末研究一下反序列化漏洞,并且搭建实战环境实际操作了一把,明白了之后发现之前听的迷糊更多是因为对于反序列漏洞思路不够清晰,明白了反序列的流程之后,反序列化漏洞很好理解。
下面的内容,我将详细论诉反序列化漏洞的利用思路。
序列化的过程
这里梳理一下正常的序列化的流程,将一个类进行序列化存储成二进制文件,然后再将该文件进行反序列化生成对象。
首先新建一个新的user类作为需要被反序列化的类使用,类截图如下图所示:
code 1:
需要被反序列化的类新建完成,现在利用writeobject()方法,将类反序列化存储在本地,具体实现过程如下:
code 2:
解释一下上述截图的代码,首先是打开一个输入流的文件对象out,然后利用writeobject方法,将user对象存储到bin.bin文件中,run test之后,文件内容如下图所示:
上图是本地存储的被实例化的对象user。
下面利用readobject方法将对象还原,具体实现过程如下图所示:
code 3:
解释一下上图的过程,首先打开bin.bin的输入流,利用readobject方法读取文件中的内容,并强制类型转换成user对象,因为此处就是user的对象序列化,因此不存在ClassNotFound异常,最后输出序列化user对象的name属性的值,run test结果如下图所示:
code 4:
readObject方法
这里我将会提到readObject方法,为什么要单独拿出来,因为如果readObject方法被反序列化的类重写,虚拟机在反序列的过程中,会使用被反序列化类的readObejct方法。
也就是说如果我在user类中重写了readObject方法,虚拟机在反序列过程中会运行user类中的readObject方法,如何证明呢?
很简单,跟着我一步步来吧,首先在user类中重写readObject方法,如下图所示:
code 5:
现在改变到此为止,利用code1的run方法,将user对象重新进行序列化,之后利用刚才code3的run2方法,将之前生成的bin.bin文件进行反序列化。
利用code1序列化截图如下:
code 6:
打开bin.bin文件截图如下图所示:
bin.bin文件生成之后再利用code 3代码,将bin.bin文件生成user对象,并且虚拟机会执行user类中重写的readobject方法,执行结果如下图所示:
code 7:
此处反序列的过程中,执行了user类中的readObject方法,我估计很多人没有认真看上面的文字描述,因此这里放上user类的全览,如下图:
code 8:
上面文章部分中,最后将code 6和code 7两幅截图联系起来理解,这里我们知道java反序列化漏洞成因是因为,需要被反序列化的类中重写了readObject方法,然而重写的readObject方法中执行了命令。
我相信有些常识的开发者都不会直接将命令写在readObject中,因此此处就需要通过反射链来进行任意代码执行了。
首先介绍几个可以被序列化的类并且重写了readobject方法,ArrayList,AnnotationInvocationHandler类等等。
因为后面文章会用到AnnotationInvocationHandler类,因此此处查看一下AnnotationInvocationHandler的源码中的readobject方法,如下图所示:
code 9:
大家先记住这个地方,我稍后再来解释这个作用。
反射链
这里我将会选用的commons-collections类中的transform链来作为反射链,详细的反射链如下图所示:
code 10:
至于为什么这里会是反射链,这里推荐大家设个断点,逐步调试查看一些源码解释就懂了,网上的解释也很详细,这里不详细说了。
由上图可知,我们得到了一个ChainedTransformer对象。这里做一个小实验,实现代码如下图所示:
code 11:
执行结果如下图所示:
从上图中我们可以看到,执行了transform方法就弹出了计算器,为什么呢?我们进入到ChainedTransformer源码中查看,transform方法如下图所示:
code 12:
上图中一切就明朗了,对transform数组依次执行transform方法,反射出了java代码执行。
这里我们找到一个类使用了ChainedTransformer类的transform方法,这个类就是TransformedMap方法,使用transform方法如下图所示:
找到valueTransformer方法的定义位置,如下图所示:
因为上述截图中的构造函数是受保护的类型,我们不能在代码中的显示的调用,因此尝试在源码中查到是否还有构造的位置,如下图所示:
这里有两个地方可以使用该构造函数,如下图所示:
构造一:
利用构造方式一的方式触发反射链:
code 13:
回到构造一的源码中,传入一个map的尺寸大于1,第一行代码会初始化valueTransformer的值,初始化之后进入到if当中,执行了TransformedMap方法的TransformMap方法,跟踪截图如下图:
最后执行了TransformValue方法,跟踪进入方法中截图如下:
这里执行了反射链的transform方法。
利用构造二触发任意代码执行,如下图所示:
code 14:
对照着源码去看很好解释,decorate方法对变量进行初始化,使用setValue方法触发反射链执行,触发点如下图所示:
上图红框中,当使用setValue方法的时候触发该方法,因此成功执行。
到此为止反射链的触发方式说完了,现在就是要寻找一个类完成触发,回到code 7的代码中,其中的membervalue在readobject方法中使用了setvalue方法,因此我们只要控制传入的Map参数是我们TransformedMap对象即可。
完成反序列漏洞实践
现在我们将构造出来的TransformedMap对象保存,AnnotationInvocationHandler作为触发类,因此也需要被序列化保存,具体实现如下图所示:
code 15:
上图中我们将AnnotationInvocationHandler构造完毕之后,序列化保存在bin2文件中,运行之后生成的bin2截图如下图所示:
利用反序列化读取该类,实现代码如下图所示:
code 16:
但是上图中并未发现远程代码执行,找了很久也没发现问题出在哪里,因此我决定自己写一个代替AnnotationInvocationHandler类,模仿该类中的setValue方法,具体实现的类全览如下图所示:
开始对上面的类进行序列化存储在本地,对code 15进行修改,从原来的AnnotationInvocationHandler修改为user类的序列化,实现过程如下:
生成的bin2文件如下图所示:
利用code 16反序列化该文件,执行结果如下图所示:
成功弹出了计算器。
结论
上文中的AnnotationInvocationHandler检查了好久没弄明白是哪里错误了,如果有人看出来了,或者知道请指出;这里利用自己模仿AnnotationInvocationHandler类实现的过程。
综合上面来看,反序列化漏洞的存在顾名思义,就是再将数据流反序列成对象的时候可以反序列成任意对象,我们不是反序列出一条反射链,而是利用反序列化漏洞反序列出反射链的触发类,通过该类去触发反射链的形成,而反射链的Map对象要在反序列化的类中可用。
技术水平有限,文章难免有不足之处,欢迎大家指正交流。可以通过微信公众号“ZeroProject”与我们取得联系,不定期分享技术文档,期待与各位大牛交流。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/aiops/54676.html