fastjson反序列化解析过程调试
简单水一下fastjson反序列化解析过程的调试
介绍
fastjson是阿里巴巴开源的java中解析json的一个组件。其功能就是将json字符串与java对象进行相互转化。正如其名,解析j转化速度很快
但fastjson多个版本都爆出反序列化漏洞,因此当遇到json时,可以看是否用了fastjson,再测是否存在漏洞
当json中含有@type字段时,fastjson会将json解析为对应类的对象
本篇文章主要是fastjson在反序列化解析的调试
调式的代码
环境:
- jdk8
- fastjson 1.2.24
导入(pom.xml)
<dependency> |
自定义的恶意类
package org.example.enity; |
这里并不是标准的JavaBean,没有cmd属性,但有setCmd方法
调试代码
String s="{\"@type\":\"org.example.enity.Test\",\"cmd\":\"calc\"}"; |
JSON.parseObject方法
这次是以parseObject的过程。parse方法稍微有些不同
具体代码
可以看出,第一步就是反序列化得到一个obj对象,后面会将该对象转为JSON对象
那么整个反序列化和解析逻辑都在这个parse方法中
具体解析的函数是在JSON类中的parse方法
前面的就是简单解析进行初始化,将json字符串封装到parser对象中
前面几层套娃(…),到这里开始解析左边界,
当解析到{时,会返回一个对象
首先会进行类加载
类加载的时候,会判断是否是一些特殊的类,然后在从从默认加载的类中寻找,如果没有才会用类加载器加载,如下
那么我们给出的类就已经加载到内存了
后面的步骤就是获取反序列化器,和进行反序列化了
首先查询是否为已经加载的反序列化器和默认类反序列化器
最后创建一个反序列化器,并将其放入derializers中,表示已经加载的反序列化器
在里面会创建一个JavaBeanInfo对象
获取getter和setter
其实这一个函数才是本次调试的核心方法
跟进build方法,首先可以看到使用反射获取我们给的类的方法属性和方法
- 获取所有setXXX(标准javaBean中存在的方法)
遍历方法数组,选取出符合条件的方法
下图为部分代码截图
筛选条件:
- 方法名长度>=4
- 不能是静态方法
- 不能是无参函数,且必须有返回
- 参数个数为1
- 方法名必须以set开头
- 不能有注解
根据这些条件能够选出setXXX,不一定是javaBean中的setXXX,只要满足条件就行(比如我定义的恶意类setCmd函数)
根据这点,后面的fastjson反序列漏洞寻找能利用的方法。
然后根据方法推断出属性名
类似的获取getXXX
筛选条件
- 非静态方法
- 无参数
- 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
如果按照普通的类(没有像调式代码的类一样)就会进入下面方法
最后根据前面获取的东西,构造JavaBeanInfo对象(会把一些构造器啥的丢进去)
然后会根据JavaBeanInfo对象构造反序列化器
进入ASMDeserializerFactory类中的createJavaBeanDeserializer:
这就是最后构造反序列化器的函数(晕*
这里开始会进行初始化操作,对象类名设置为fastjson自定义的类名
然后反射newInstance构造一个实例返回
至此原来的createJavaBeanDeserializer函数结束。
整个获取反序列器的步骤结束
发现后面代码调试不了,这是因为前面ASMDeserializerFactory 是用利用ASM重新生成类(ASM操作字节码),因为这个反序列化器类是代码生成的,在内存中,所以无法使用idea调试后面的反序列化操作
解决不能调试
查看在最后之前的代码
起初asmEnable为 true,会走下面代码(也就是前面不能调式的代码)。如果asmEnable是false,就会直接创建一个JavaBean反序列化器(就不需要使用ASM创建一个临时的类了)。这样,后面代码可以继续调试。
这里有几种方式
发现getOnly为true更适合(其他的是要求 : 类是非public、要是一个接口、属性个数大于200)
查找getOnly,发现只有一处为false
是一个Feildinfo的构造方法
这里看出方法的参数个数不能为1
发现只有在前面JaavaBean.build的时候
遍历获取getter方法的时候,才满足条件
进入if后,会调用这个new FeildInfo时进入上面的设置getOnly为true
add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, 0, 0, 0, annotation, null, null)); |
这里进入if就行了
这里是要方法的返回值是这些类中的其中一个,只有前面两个好实现,collection类和map类
除此之外,也不能有之对应的setxxx,如果存在,就不会在getxxx(第二次循环时候)被解析
综上:
类中需要一个返回类型是map类型getxxx方法(且不能有对应的setxxx),才能对后续代码进行调试
因此调试代码中有mymap属性和getMymap方法
然后继续
进入默认的反序列化器JavaBeanDeserializer中进行反序列化,其中下面代码会触发setXXX方法
后面也没什么了,出反序列化方法
如果是parseObject方法。 最后会进行json对象的序列化
然后使用JavaBeanSerializer序列化成json对象
在其中,会调用所有属性的getter方法
这里是使用反射调用的getter方法
总结
通过调试可以发现
JSON.parseObject方法是
- 会先会获取反序列化器,同时通过反射获取所有的setXXX和getXXX
- 将所有属性和值放入反序列化器
- 反序列化的时候会调用该属性的setXXX设置对象属性
- 若是parseObject方法(除去parse方法),会转为json对象时会调用getter方法
因此上面我们的代码中
我们定义的类没有cmd属性,但我们传入cmd键时,反序列化时触发setcmd方法,因此造成了命令执行。
除此,也要区分parse和parseObject,前者不会触发getXXX
最后,想说好久没更新了文章了。主要是最近好一段时间都没怎么深入投入学习,并有各种事情忙。(我想立flag了
初识java安全,确实我也是小白