Java-Fastjson 源码分析
0x01 写在前面
说来惭愧,从 JNDI 开始的文章几乎都是跟着 Drunkbaby 师傅的博客走一遍利用链,自己的思考少了很多,这种脑袋空空的学习是很轻松的,但是学完以后心里也不不踏实,虽然这篇文章仍然是参考 Drunkbaby 师傅,但是我希望能融入自己的思考
0x02 Fastjson 原理浅析
由前面的学习我们知道,完成 Fastjson 反序列化的方法有两种:
parseObject():返回fastjson.JSONObject类parse():返回指定类(如下 People 类)

但是我们可以向 parseObject() 中传递 类的class 参数(demo 中为 Object.class 或 People.class)达到和 parse() 同样的效果:

这里我们深入看一下 JSON 类里面的 parseObject() 和 parse() 方法,它们被重写了很多,参数大同小异我们拿常用的进行分析:

parse 方法解析
1 | public static Object parse(String text, Feature... features) { |
这里传入两个参数,一个是 String text,也就是我们要拿去反序列化解析的字符串,另外一个参数是 Feature,Feature 并没有什么特别大的用处,如果没有选择该 Feature,那么在反序列的过程中,Fastjson 会自动把下划线命名的 json 字符串转化到驼峰式命名的 Java 对象字段中
这里有个调试过程中值得注意的点,parse() 与 parseObject``() 方法一样,在反序列化时实际上两者都会走到 DefaultJSONParser#parseObject

parseObject 方法解析
比较重要的自然是第二个参数 clazz,也就是我们前面在 demo 中传入的 Object.class,实际上对于很多开发者,为了省事,会在第二个参数传入 Object.class
1 | public static <T> T parseObject(String text, Class<T> clazz) { |
具体的就不再赘述了,在实际开发场景当中 parseObject() 用到的非常多,基本全部都是
下面我们详细看一看为什么 Fastjson 在反序列化的时候会自动调用 getter/setter 方法,同时也过一遍 Fastjson 反序列化的流程
getter/setter 自动调用
断点下在 DefaultJSONParser#parseObject 里面的 derializer.deserialze() 就好,前面的过程都是在互相调用,可以直接看这里的业务层,跟进

这里做的业务比较特殊:如果传进来要解析的类是 Array 数组类,就直接进行 newInstance() 的实例化,并且将数组数据返回,如果传进来的不是 Array 数组类,而是 Object 类,或是不可以反序列化的类,就会走到下一个业务方法的 parseObject() 里面
传进来的不是 Array 数组类,而是 Object 类,或是不可以反序列化的类,就会走到下一个业务方法的 parseObject() 里面,这里我们的 type 为 Object.class 所以不满足条件,走到 parser.parse(fieldName)

跳出这个方法后,我们又进入新的 DefaultJSONParser#parseObject,业务流程是先做了一个基础的判断 token 是否相同

DefaultJSONParser 类的 367 行,把 deserializer 的值拿出来,赋给 deserializer,跟进一下

跟过来是一个方法,继续往下跟进具体的业务方法

前面都是做了很多数据处理,到第 461 行这里,跟进 createJavaBeanDeserializer()

继续往下走,终于才能看到我们的主角登场了 JavaBeanInfo#build

JavaBeanInfo#build 先通过反射获取我们 @type 里面要去反序列化的那个类的一些基本信息

328 行开始有一个 for 循环,当中把 @type 类对应的 Methods 全部拿出来

首先会遍历 methods 中所有的 method 方法,然后会经过四个判断,只要符合任意一个判断就会触发 continue 跳出当前循环,所以必须要满足下面列出来的五个方法才能顺利执行,否则就会跳出当前循环
- 方法名长度不能小于 4
- 不能是静态方法
- 返回的类型必须是 void 或者是自己本身
- 传入参数个数必须为 1
- 方法开头必须是 set
最后将可用的 setter 方法放到 fieldList 里面:

此时,在 fieldList 当中,有两个 FieldInfo,这也就是我们的三个 setter 方法:SetName() SetAge()

下面我们来看 getter 方法是如何被获取到的,和 setter 大同小异,首先会遍历 methods 中的每个方法,同样的如果要顺利执行下去需要符合四个条件
- 方法名长度不小于 4
- 不能是静态方法
- 方法名要 get 开头同时第四个字符串要大写
- 方法返回的类型必须继承自 Collection Map AtomicBoolean AtomicInteger AtomicLong
- 传入的参数个数需要为 0

最后返回 JavaBeanInfo,beanInfo 中会存放我们类中的各种信息

后续,在 JavaBeanDeserializer#deserialze 方法中,对 FieldInfo 进行循环遍历,将每一个可用的 setter 与 getter 方法拿出来在其 593 行,跟进 fieldDeser.setValue(object, fieldValue) 里面就是反射调用方法的语句:

0x03 Fastjson 其他点解析
这部分前面的文章其实讲过了,这里点到为止
Feature.SupportNonPublicField
由于该字段在 fastjson1.2.22 版本引入,所以只能影响 1.2.22-1.2.24
在前面的 Evil 类中,可以发现我们的 setter 和 getter 方法是 public 的,但是如果有时候遇到 private 的情况我们就不能进行反序列化了,会返回 null
这时我们可以添加 SerializerFeature.WriteClassName 参数,这样私有属性 _bytecodes ,_tfactory 就会被 fastjson 正常反序列化了
关于 checkAutoType
- 这其实是从 Fastjson 1.2.24 版本之后出来的修复点。
checkAutoType() 函数就是使用黑白名单的方式对反序列化的类型继续过滤,acceptList 为白名单(默认为空,可手动添加),denyList 为黑名单(默认不为空)。
默认情况下,autoTypeSupport 为 False,即先进行黑名单过滤,遍历 denyList,如果引入的库以 denyList 中某个 deny 开头,就会抛出异常,中断运行。
true: 先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤
false: 先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错
0x04 多 @type 通杀型 EXP
在运行完毕后会返回一个 obj,这个 obj 里面是类名以及其对应的属性值,这个属性值会保存到 map 里面
这个 map 放了原本的值,也就是我们说的缓存,可以通过 TypeUtils.getClassFromMapping() 来加载
1 | import com.alibaba.fastjson.JSON; |
具体分析:Java-Fastjson 反序列化各版本绕过分析
参考:





