前言
Fastjson 是阿里开发的一个 Java 库,用于将 Java 对象序列化为 JSON 格式,也可将字符串反序列化为 Java 对象。
Fastjson 是非常多见的 Java 反序列化漏洞,CTF中也出现的非常频繁。
Fastjson 1.2.24
Fastjson 1.2.24 版本的反序列化漏洞在 2017年 首次爆出,之后更是难以平息。
Fastjson 序列化对象的方法主要是toJSONString
方法,而反序列化还原对象的方法有3个
parseObject(String text)
parse(String text)
parseObject(Srting text, Class\clazz)
在序列化和反序列化的过程中会自动去调用反序列化对象中的 getter、setter方法以及构造函数,这就是 Fastjson 反序列化漏洞产生的原因,接下来我们来具体来看一下。
探究前的准备
首先我们先在pom.xml
导入fastjson 1.2.24
接下来我们定义如下的类来进行测试:
属性 | getter | setter |
---|---|---|
public String t1 | 有 | 有 |
private int t2 | 有 | 有 |
private boolean t3 | 有 | 无 |
private Map t4 | 有 | 无 |
private Map t5 | 有 | 有 |
探究序列化的过程
JSON.toJSONString(String)
在序列化字符串的时候,会调用类中所有的getter
方法,我们发现最后产生的序列化字符串和平时见的有些不一样。
其实我们只需要在该方法中加入参数SerializerFeature.WriteClassName
方法,传入SerializerFeature.WriteClassName
可以使得Fastjson支持自省,开启自省后序列化成JSON
的数据就会多一个@type,这个是代表对象类型的JSON
文本。FastJson的漏洞就是他的这一个功能去产生的,在对该JSON数据进行反序列化的时候,会去调用指定类中对于的get/set/is方法。
探究反序列化过程
JSON.parse(jsonstr)
按照之前序列化的情况类比推理一下,猜测被调用的方法应该是setT1、setT2和setT5
(因为T3和T4
没有setter
方法),然后我们执行一下该方法,看看是不是和想象中的一样
很明显,我们的猜测也不是全无道理,只不过为什么在反序列化的过程中调用了getT4
呢!我们先保留这个问题继续往下看!
JSON.parseObject(jsonstr, FastjsonTest.class)
这次我们直接执行该方法,发现它和上一个方法的结果一样!
通过上网查阅资料,需要满足下面的条件时,getter
方法就会被调用
- 这个
getter
方法不存在setter
方法 - 返回值类型继承自Collection || Map || AtomicBoolean || AtomicInteger ||AtomicLong
上边这俩个原因是主要原因,这也就是为什么只调用了getT4
而没有调用getT3
,其他调用条件应该算是getter
或setter
方法的必须条件
方法名需要长于4
非静态方法
以 get 字符串开头,且第四个字符需要是大写字母
方法不能有参数
方法为 public 属性
JSON.parseObject(jsonstr)
我们再来看看最后一个方法,竟然执行了这么多
仔细看一看,其实不难发现,调用的前四个方法和之前那俩个一样,然后接着调用了所有的getter
方法,所以也就不难理解了
反序列化后的类型
我们再查看三种反序列化方法的返回类型
我们通过调试查看了这三种方法返回的类型,发现JSON.parseObject(jsonstr)
方法还真是独特,分析得知:
parseObject(String text)
其实就是执行了parse()
,随后将返回的Java对象通过JSON.toJSON()
转为JSONObject对象。
总结
JSON.parse(jsonstr) | JSON.parseObject(jsonstr, FastjsonTest.class) | JSON.parseObject(jsonstr) | |
---|---|---|---|
T1 | setter | setter | setter getter |
T2 | setter | setter | setter getter |
T3 | getter | ||
T4 | getter | getter | getter getter |
T5 | setter | setter | getter |
上述为测试结果,下边为属性的类型和有无getter/setter
属性 | getter | setter |
---|---|---|
public String t1 | 有 | 有 |
private int t2 | 有 | 有 |
private boolean t3 | 有 | 无 |
private Map t4 | 有 | 无 |
private Map t5 | 有 | 有 |
- 反序列化过程中会调用所有的
setter
方法 - 如果没有
setter
方法,会调用满足条件的getter
方法
方法名需要长于4
非静态方法
以 get 字符串开头,且第四个字符需要是大写字母
方法不能有参数
返回值类型继承自Collection \|\| Map \|\| AtomicBoolean \|\| AtomicInteger \|\|AtomicLong
getter 方法对应的属性只能有 getter 不能有setter方法
方法为 public 属性
happyFastjson
可以很容易的找到fastjson反序列化的入口
发现FlagBean
类中可以读取到flag
于是构造payload
poc={
"a":{
"@type":"com.ctfshow.happyfjs.Beans.FlagBean",
"flag":{
"@type":"java.util.HashMap"
}
}
}
该payload现实经过JSON.parse(poc)
调用了所有setter
方法
- 这里没有
setFlag
方法,并且满足其他执行getFlag
的条件,所以执行了getFlag
方法 - 序列化字符串中没有
free
属性,所以不执行setFree
方法
此时因为this.count=1
所以并没有执行系统命令,然后这里又对返回的对象进行了toString
方法,通过调试发现,实际上这里还是调用了toJSONString
方法
所以相当于再次进行了序列化操作,所以会执行所有的getter
方法,然后就会输出flag
这里我尝试将poc
修改为如下:
poc={
"@type":"com.ctfshow.happyfjs.Beans.FlagBean",
"flag":{
"@type":"java.util.HashMap"
}
}
通过调试发现调用的是一下方法
这个方法是类对象的默认toString
方法,我们可以通过覆写来实现新的toString
方法