Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。
项目地址:https://github.com/alibaba/fastjson
Student.java
public class Student { private String name; private int age; public Student() { System.out.println("构造函数"); } public String getName() { System.out.println("getName"); return name; } public void setName(String name) { System.out.println("setName"); this.name = name; } public int getAge() { System.out.println("getAge"); return age; } public void setAge(int age) { System.out.println("setAge"); this.age = age; } }
然后通过Ser.java进行序列化
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; public class Ser { public static void main(String[] args){ Student student = new Student(); student.setName("ghtwf01"); student.setAge(80); String jsonstring = JSON.toJSONString(student, SerializerFeature.WriteClassName); System.out.println(jsonstring); } }
SerializerFeature.WriteClassName
是toJSONString
设置的一个属性值,设置之后在序列化的时候会多写入一个@type
,即写上被序列化的类名,type
可以指定反序列化的类,并且调用其getter/setter/is
方法。
没加SerializerFeature.WriteClassName
时
上面说了有parseObject和parse两种方法进行反序列化,现在来看看他们之间的区别
public static JSONObject parseObject(String text) { Object obj = parse(text); return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj); }
parseObject其实也是使用的parse方法,只是多了一步toJSON方法处理对象。
看下面几种反序列化方法
一二种方法没用成功反序列化,因为没有确定到底属于哪个对象的,所以只能将其转换为一个普通的JSON对象而不能正确转换。所以这里就用到了@type
,修改后代码如下
这样便能成功反序列化,可以看到parse成功触发了set方法,parseObject同时触发了set和get方法,因为这种autoType
所以导致了fastjson反序列化漏洞
我们知道了Fastjson的autoType,所以也就能想到反序列化漏洞产生的原因是get或set方法中存在恶意操作,以下面demo为例
Student.java
import java.io.IOException; public class Student { private String name; private int age; private String sex; public Student() { System.out.println("构造函数"); } public String getName() { System.out.println("getName"); return name; } public void setName(String name) { System.out.println("setName"); this.name = name; } public int getAge() { System.out.println("getAge"); return age; } public void setAge(int age) { System.out.println("setAge"); this.age = age; } public void setSex(String sex) throws IOException { System.out.println("setSex"); Runtime.getRuntime().exec("open -a Calculator"); } }
Unser.java
import com.alibaba.fastjson.JSON; public class Unser { public static void main(String[] args){ String jsonstring ="{\"@type\":\"Student\":\"age\":80,\"name\":\"ghtwf01\",\"sex\":\"man\"}"; //System.out.println(JSON.parse(jsonstring)); System.out.println(JSON.parseObject(jsonstring)); } }
在parseObject处下断点,跟进
public static JSONObject parseObject(String text) { Object obj = parse(text); return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj); }
第一行将json字符串转化成对象,跟进parse
public static Object parse(String text) { return parse(text, DEFAULT_PARSER_FEATURE); }
继续跟进
public static Object parse(String text, int features) { if (text == null) { return null; } else { DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features); Object value = parser.parse(); parser.handleResovleTask(value); parser.close(); return value; } }
这里会创建一个DefaultJSONParser对象,在这个过程中有如下操作
int ch = lexer.getCurrent(); if (ch == '{') { lexer.next(); ((JSONLexerBase)lexer).token = 12; } else if (ch == '[') { lexer.next(); ((JSONLexerBase)lexer).token = 14; } else { lexer.nextToken(); }
判断解析的字符串是{还是[并设置token值,创建完成DefaultJSONParser对象后进入DefaultJSONParser#parse方法
因为之前设置了token值为12,所以进入如下判断
case 12: JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); return this.parseObject((Map)object, fieldName);
在第一行会创建一个空的JSONObject,随后会通过 parseObject 方法进行解析,在解析后有如下操作
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) { ref = lexer.scanSymbol(this.symbolTable, '"');