
Fastjson简介
Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。
Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象。
Fastjson 源码地址:https://github.com/alibaba/fastjson
Fastjson 中文 Wiki:https://github.com/alibaba/fastjson/wiki/Quick-Start-CN
提供两个主要接口来分别实现序列化和反序列化操作。
JSON.toJSONString
将 Java 对象转换为 json 对象,序列化的过程。
JSON.parseObject/JSON.parse
将 json 对象重新变回 Java 对象;反序列化的过程
能够把json理解成字符串
Demo
简单看一下是怎么序列化反序列化的
先导入依赖
1 | <dependency> |
先写一个human类
1 | public class human { |
序列化
写一个humanSerialize
1 | import com.alibaba.fastjson.JSON; |
下个断点看这个过程
会先到JSON这个类,然后toJSONString()
->new SerializeWriter()
,序列化在这里完成的,里面定义了一些初值,赋值给 out 变量,这个 out 变量后续作为 JSONSerializer
类构造的参数
JSON里面有很多个static变量,其中就有一个DEFAULT_TYPE_KEY
值为@type
然后后面就是toString了,看一下运行结果
1 | 构造函数 |
代码重点在
1 | String jsonString = JSON.toJSONString(human, SerializerFeature.WriteClassName); |
主要是第二个参数,SerializerFeature.WriteClassName
是 JSON.toJSONString()
里一个设置属性值,设置之后可以在后面序列化数据多加一个@type,也就是写上被序列化的类名,type可以指定反序列化的类,并调用getter
/setter
/is
方法。
Fastjson 接受的 JSON 可以通过@type字段来指定该JSON应当还原成何种类型的对象,在反序列化的时候方便操作。
没有设置SerializerFeature.WriteClassName
是输出:
1 | 构造函数 |
反序列化
1 | import com.alibaba.fastjson.JSON; |
运行结果就这样
1 | 构造函数 |
大概就是这么用
一些基础知识
反序列化时的 Feature.SupportNonPublicField 参数
前面示例中反序列化的时候没办法获取到age的值,因为他是private的,如果要还原出private属性的话,需要在JSON.parseObject
/JSON.parse
中加上Feature.SupportNonPublicField参数
现在把human类里面的setAge函数注释掉,此时name是public,age是private,做一个对比
1 | ===没有Feature.SupportNonPublicField=== |
所以总的来说,把json传到JSON.parseObject()进行反序列化,如果要内容里面的私有变量被成功地还原出来,要加上Feature.SupportNonPublicField参数
只进行 JSON.parseObject(jsonString)
看一下parseObject()的指定或不指定对反序列化类型之间的差异
修改human类,添加两个private成员变量,且所有的私有成员变量都不定义setter方法
1 | import java.util.Properties; |
现在的反序列化代码
1 | import com.alibaba.fastjson.JSON; |
输出
1 | 构造函数 |
可以看到调用了构造函数,public属性变量的setter函数,所有变量的getter函数,getProperties还被调用了两次,最后得到的obj是JSONObject类型,这算反序列化失败了
现在把语句改成
1 | Object obj = JSON.parseObject(jsonString,human.class); |
输出变成,这样就算成功了
1 | 构造函数 |
我问ai为什么,它说
是的,将代码改为
Object obj = JSON.parseObject(jsonString, human.class);
后,Fastjson 能够成功将 JSON 反序列化为human
类的对象。以下是具体原因和机制分析:1. 显式指定目标类的作用
通过
JSON.parseObject(jsonString, human.class)
明确告知 Fastjson 目标类,此时 Fastjson 的行为会发生变化:
- 绕过
@type
的自动解析:即使 JSON 中包含@type
字段,Fastjson 也会优先使用你传入的human.class
作为目标类。- 直接实例化
human
对象:Fastjson 会通过反射创建human
类的实例,并尝试将 JSON 字段映射到该类的字段或 setter 方法。2. 原代码失败的原因
原代码
JSON.parseObject(jsonString)
未指定目标类,Fastjson 会尝试根据 JSON 中的@type
字段动态加载类:
- 依赖
@type
的类加载机制:@type
字段的值human
需要对应一个全限定类名(如com.example.human
),且该类必须在类路径中可访问。- 原代码的问题:
- 如果
human
类未正确定义或未被正确加载,Fastjson 无法找到该类,会回退到通用JSONObject
。- 即使
human
类存在,某些 Fastjson 版本默认禁用@type
功能(出于安全考虑),导致动态类加载失败。
parse与parseObject区别
两者主要的区别就是parseObject()
返回的是JSONObject而parse()
返回的是实际类型的对象,当在没有对应类的定义的情况下,一般情况下都会使用JSON.parseObject()
来获取数据。
FastJson中的
parse()
和parseObject()
方法都可以用来将JSON字符串反序列化成Java对象,parseObject()
本质上也是调用parse()
进行反序列化的。但是parseObject()
会额外的将Java对象转为 JSONObject对象,即JSON.toJSON()
。所以进行反序列化时的细节区别在于,parse()
会识别并调用目标类的setter
方法及某些特定条件的getter
方法,而parseObject()
由于多执行了JSON.toJSON(obj)
,所以在处理过程中会调用反序列化目标类的所有setter
和getter
方法。
所以使用parse()的时候他会自动反序列化得到特定的类,不用想parseObject()一样指定第二个参数,看看效果
现在的代码是
1 | public class humanUnSerialize { |
输出
1 | 构造函数 |
所以就是要用 parseObject
,里面的参数需要是 Object.class
Fastjson反序列化原理
fastjson 在反序列化的时候会去找我们在
@type
中规定的类是哪个类,然后在反序列化的时候会自动调用这些 setter 与 getter 方法的调用,注意!并不是所有的 setter 和 getter 方法。
fastjson会对下列要求的setter和getter方法进行调用
满足条件的setter:
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
满足条件的getter:
- 非静态方法
- 无参数
- 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
漏洞原理
通过fastjson反序列化漏洞,攻击者可以传入恶意构造的json内容,程序对他反序列化后得到恶意类执行力恶意类中的恶意函数,从而导致代码执行
fastjson用parse()/parseObject()反序列化的时候可以指定类型,如果刚好指定的类型有很多子类,有很多子函数,就比较有利用空间。
例如,如果指定类型为Object或JSONObject,则可以反序列化出来任意类。例如代码写
Object o = JSON.parseObject(poc,Object.class)
就可以反序列化出Object类或其任意子类,而Object又是任意类的父类,所以就可以反序列化出所有类。
如何触发恶意函数?
某种情况下,反序列化后会把反序列化得到的类中的构造函数,getter,setter都执行一遍,如果这三个里面存在恶意函数,这就是反序列化漏洞所在!所以触发方式就在这三个里面
所以利用反序列化漏洞的关键:
- 某个类的构造函数、
setter
方法、getter
方法中的某一个存在危险操作,比如造成命令执行; - 可以控制该漏洞函数的变量(一般就是该类的属性);
Poc
如果反序列化指定的类型的是Object.class,也就是代码是
1 | Object obj = JSON.parseObject(jsonstring, Object.class, Feature.SupportNonPublicField); |
反序列化得到的类的构造函数、所有属性的setter
方法、properties私有属性的getter
方法都会被调用
现在做个尝试,改一下human.java里面的getter函数
1 | public Properties getProperties() throws IOException { |
再执行代码就跳出计算机
1 | import com.alibaba.fastjson.JSON; |
当前反序列化里面指定的类是Object.class,他是所有类的父类,所以他的子类的human的getter函数存在恶意函数,当@type指向human类是反序列化就会触发漏洞
如果你知道这个类本事就有问题,直接指定类名也是可以的
1 | Object obj = JSON.parseObject(jsonString,human.class,Feature.SupportNonPublicField); |
看代码触发语句就是