介绍
fastjson是一款有阿里巴巴公司开发的高性能JSON解析库
JSON.toJSONString 将 Java 对象转换为 json 对象,序列化的过程。
JSON.parseObject/JSON.parse 将 json 对象重新变回 Java 对象;反序列化的过程
使用
JSON序列化
代码
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency>
|
demo.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package fastjson;
import com.alibaba.fastjson.JSON;
public class demo { public static void main(String[] args) { person pysnow = new person(1); pysnow.setName("pysnow"); pysnow.setAge(20);
String jsonString = JSON.toJSONString(pysnow); System.out.printf(jsonString); } }
class person{ public int id; private String name;
public person(int id) { this.id = id; }
private int age;
public person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; } }
|
代码调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public static String toJSONString(Object object, // SerializeConfig config, // SerializeFilter[] filters, // String dateFormat, // int defaultFeatures, // SerializerFeature... features) { SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);
try { JSONSerializer serializer = new JSONSerializer(out, config); if (dateFormat != null && dateFormat.length() != 0) { serializer.setDateFormat(dateFormat); serializer.config(SerializerFeature.WriteDateUseDateFormat, true); }
if (filters != null) { for (SerializeFilter filter : filters) { serializer.addFilter(filter); } }
serializer.write(object);
return out.toString(); } finally { out.close(); } }
|
具体实现逻辑为以上代码
可以看到此时的调用堆栈如上,此时多了一个static静态变量”member of fastjson”
接着我们分析代码可以知道首先他SerializeWriter对象用于存储JSON序列化出来的输出流,接着创建JSONSerializer对象,该对象是真正将Object对象转化成字符串的对象,其接受了输出流对象SerializeWriter和配置对象SerializeConfig,其中SerializeConfig对象是默认传入的不用管
接着我们可以看到toJSONString该方法传入两个参数时第二个参数为SerializerFeature… features,也就是最后一个参数
他在创建SerializeWriter对象是进行了调用,跟随SerializeWriter对象进入构造函数的
这里的逻辑就是遍历传入的features值然后与默认的feature值进行或运算,将最终的特征值赋值给this.features
最后回到toJSONString方法这里,调用了JSONSerializer的write方法传入了要序列化的对象
刚方法先获取了要序列化对象的Class对象,接着创建对于对象类的JavaBeanSerializer对象包含了对于javabean的基础信息(类名,字段等),以及所有的getter方法
最后调用该JavaBeanSerializer的write方法,该方法就是通过调用对象的getter方法获取对象的属性值然后写到字符串中
最后调用输出流对象的toString方法将SerializeWriter对象的buf值返回出来
接着我们使用两个参数进行JSON序列化
1
| String jsonString = JSON.toJSONString(pysnow, SerializerFeature.WriteClassName);
|
添加的WriteClassName参数是直接进入了SerializeWriter对象的构造方法里面,他会影响JSON序列化出来的结果,也就是会多打印一个”@type”:”类名”,主要代码逻辑在write方法里面如下
他这里会通过serializer.isWriteClassName(fieldType, object)判断是否有WriteClassName这个字段,如果有则调用writeClassName(serializer, object);方法写进serializer对象里面
最终得到的序列化字符串如下
这里的输出顺序可以发现先是age然后才是int字段,不是安装创建的顺序
这是因为JSON对象对其字符进行了排序
JSON反序列化
代码
demo.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package fastjson;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature;
public class demo { public static void main(String[] args) { ser(); unser(); }
public static void ser() { person pysnow = new person(1); pysnow.setName("pysnow"); pysnow.setAge(20);
String jsonString = JSON.toJSONString(pysnow, SerializerFeature.WriteClassName); System.out.println(jsonString); }
public static void unser() { String payload = "{\"@type\":\"fastjson.person\",\"age\":20,\"id\":1,\"name\":\"pysnow\"}"; person pysnow = (person) JSON.parseObject(payload, person.class); System.out.printf(pysnow.toString()); } }
|
person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| package fastjson;
public class person{ public int id; private String name;
private int age;
@Override public String toString() { return "person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; }
public person() { }
public person(int id) { this.id = id; } public person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; } }
|
代码调试
这里首先创建了DefaultJSONParser对象将各种参数放了进去,接着调用他的parseObject方法传入Class对象
这里根据@type字段创建了对于的反序列化器config.getDeserializer(type)
里面调试的deserialize方法的时候不知道怎么定位不到源代码,就直接静态分析了
很明显这里是先使用了person默认的无参构造器创建对应的对象,然后调用setter方法为对象进行赋值
这里有一些对于特殊字符串的处理如@ref
,这个字段就是后面fastjson进行攻击的时候所需要用到的
这里是使用默认构造器创建对应对象的代码
至于这么给对象赋值的,由于deserialize跳不进去,并且静态分析代码又太多了,我们可以直接给setter加上输出来判断
可以看到默认不加Feature.SupportNonPublicField时private属性是通过setter方法来进行赋值的
而int字段的setter方法是存在的但是没有通过他来进行赋值,所以可以说明public属性默认是通过反射来进行赋值的,如果遇到private属性且没有Feature.SupportNonPublicField字段则就通过寻找setter方法来进行赋值
Feature.SupportNonPublicField:用来支持没有被public的属性进行反射赋值
注意问题
parseObject只加一个参数
这里我们把代码改成parseObject(String),不加第二个Class参数看一下会有什么问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| package fastjson;
public class person { public int id; private String name; private int age; private int money;
public int getMoney() { System.out.println("getMoney"); return money; }
public void setName(String name) { System.out.println("nameSet"); this.name = name; }
@Override public String toString() { return "person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; }
public person() { }
public person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; }
public int getId() { System.out.println("IDget"); return id; }
public void setId(int id) { System.out.println("IDset"); this.id = id; }
public String getName() { System.out.println("nameGet"); return name; }
public int getAge() { System.out.println("getAge"); return age; }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package fastjson;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature;
public class demo { public static void main(String[] args) { unser(); }
public static void ser() { person pysnow = new person(1, "pysnow", 20);
String jsonString = JSON.toJSONString(pysnow, SerializerFeature.WriteClassName); System.out.println(jsonString); }
public static void unser() { String payload = "{\"@type\":\"fastjson.person\",\"age\":20,\"id\":1,\"name\":\"pysnow\",\"money\":200}"; JSONObject pysnow = JSON.parseObject(payload); System.out.println(pysnow.toString()); System.out.println(pysnow.getClass().getName()); } }
|
可以看到最后反序列化出来的对象是JSONObject对象
不是我们想要反序列化的对象,并且在反序列化的时候他在构建完该对象后又调用所有属性的getter方法
接着我加上第二个参数,也就是反序列化对应的Class类,可以发现成功将person对象反序列化出来了,而且在构建完对象也没有调用所有的getter方法
这里调用所有对象的getter方法的原因是parseObject在没有指定要反序列化的Class类时会自动在反序列化出后执行一次JSON.toJSON
将解析出来的对象转化为JSONObject对象,而这个转化的过程就会触发对应所有字段的getter方法
parseObject于parse区别
这里前面其实也提到了一点原因,parse方法只能传入一个String参数,区别于parseObject,parse方法反序列化的对象就为原来的对象不会转化成JSONObject
所以在不知道反序列化出来的实际类时选择使用parseObject要比parse安全性要高一些,所以一般都选择使用parseObject来解析未知的JSON数据,但是这个方法在转化的时候又会调用对应的getter方法,所以标准的解析代码应该使用parseObject(String,Class)
,加上具体.Class对象
1
| Object pysnow = JSON.parseObject(payload,具体.class);
|
可以看到在不知道Class类时parseObject方法实际上就是调用了parse方法来解析,并且在前面加上了一个(JSONObject)
的强制转化
反序列化漏洞
fastjson在反序列化的时候会根据Json字符串里面的@type
字段去寻找对于的Class对象,然后调用对应的setter或者getter方法,而对应的getter方法或者setter如果存在漏洞之内的代码就可以作为反序列化触发的起点,类似于原生反序列化的readObject
满足fastjson的getter/setter规则如下(其实就是JavaBean的规则)
满足条件的setter:
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
满足条件的getter:
- 非静态方法
- 无参数
- 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
fastjson反序列化漏洞的目的就是传入一个恶意的对象json流,然后反序列化出来的恶意对象并调用该对象的恶意函数(getter/setter/construct)。
构造恶意反序列化对象
通过前面分析可以知道parse
和parseObejct(String,Object.Class)
时我们可以反序列化出任意的恶意对象,其中第一个parse他是默认反序列化原始对象类型,而后面那个因为指定的Class类时Obejct,而Object又是所有类的父类所有也是可以正常调用的
触发恶意方法
也就是自动触发的方法,fastjson在反序列化的时候总共会自动触发以下三个方法
construct()
setter()
getter()
若反序列化指定类型的类如Student obj = JSON.parseObject(text, Student.class);,该类本身的构造函数、setter方法、getter方法存在危险操作,则存在Fastjson反序列化漏洞;
若反序列化未指定类型的类如Object obj = JSON.parseObject(text, Object.class);,该若该类的子类的构造方法、setter方法、getter方法存在危险操作,则存在Fastjson反序列化漏洞;
PS:这里会自动调用getter的原因是当我们将反序列化类的某个返回类型为Object的属性赋值时,就会调用对应的getter
这里的Properties属性是符合getter的要求继承与hashtable的
那这里其他属性比如age,money这些没有调用getter方法是因为他们不符合getter的条件
需要返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
总结
其实就是传入一个JSON字符串,在解析反序列化的时候会自动调用getter和setter方法,然后我们传入一个服务端有的对象,其中的getter后者setter方法包含恶意代码,造成fastjson反序列化漏洞
参考文章
https://drun1baby.top/2022/08/04/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Fastjson%E7%AF%8701-Fastjson%E5%9F%BA%E7%A1%80