Java代码审计之Jackson序列化和反序列化
注:本文为复现文和学习文,原创极少,参考了大量参考链接里的内容~感兴趣者自行阅读原作
0x01 序列化和反序列化
Jackson是一个开源的Java序列化和反序列化工具,可以将Java对象序列化为XML或JSON格式的字符串,以及将XML或JSON格式的字符串反序列化为Java对象。
Jackson提供了ObjectMapper.writeValueAsString()
和ObjectMapper.readValue()
两个方法来实现序列化和反序列化的功能。
Person.java
public class Person {
public int age;
public String name;
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s", age, name);
}
}
JSTest.java
package com.DemoJackson.Study.Demo;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.age = 6;
p.name = "mi1k7ea";
ObjectMapper mapper = new ObjectMapper();
// 序列化
String json = mapper.writeValueAsString(p);
System.out.println(json);
// 反序列化
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
结果
{"age":6,"name":"mi1k7ea"}
Person.age=6, Person.name=mi1k7ea
0x02 多态 JacksonPolymorphicDeserialization
DefaultTyping和@JsonTypeInfo注解
1. DefaultTyping
public static enum DefaultTyping {
JAVA_LANG_OBJECT,
OBJECT_AND_NON_CONCRETE,
NON_CONCRETE_AND_ARRAYS,
NON_FINAL;
private DefaultTyping() {
}
}
DefaultTyping的几个设置选项是逐渐扩大适用范围的,如下表:
DefaultTyping类型 | 描述说明 |
---|---|
JAVA_LANG_OBJECT | 属性的类型为Object |
OBJECT_AND_NON_CONCRETE | 属性的类型为Object、Interface、AbstractClass |
NON_CONCRETE_AND_ARRAYS | 属性的类型为Object、Interface、AbstractClass、Array |
NON_FINAL | 所有除了声明为final之外的属性 |
1-1. JAVA_LANG_OBJECT
JAVA_LANG_OBJECT:当被序列化或反序列化的类里的属性被声明为一个Object类型时,会对该Object类型的属性进行序列化和反序列化,并且明确规定类名。(当然,这个Object本身也得是一个可被序列化的类)
Hacker.java
package com.DemoJackson.Study.DemoDefaultTyping.Demo1JavaLangObject;
public class Hacker {
public String skill = "Jackson";
}
Person.java
package com.DemoJackson.Study.DemoDefaultTyping.Demo1JavaLangObject;
public class Person {
public int age;
public String name;
public Object object;
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
}
}
JSTest.java
// 通过enableDefaultTyping()设置设置JAVA_LANG_OBJECT后,会多输出Hacker类名
// 且在输出的Object属性时直接输出的是Hacker类对象,也就是说同时对Object属性对象进行了序列化和反序列化操作
package com.DemoJackson.Study.DemoDefaultTyping.Demo1JavaLangObject;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.age = 6;
p.name = "mi1k7ea";
p.object = new Hacker();
// 设置JAVA_LANG_OBJECT
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
String json = mapper.writeValueAsString(p);
System.out.println(json);
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
结果输出了Hacker对象,说明对Person.Object的对象Hacker序列化和反序列化了。
1-2. OBJECT_AND_NON_CONCRETE
OBJECT_AND_NON_CONCRETE:不仅仅对Object类,对Interface、AbstractClass类也会进行序列化和反序列化(当然这些类本身需要时合法的、可被序列化的对象)。此外,enableDefaultTyping()默认的无参数的设置就是此选项。
Sex.java
package com.DemoJackson.Study.DemoDefaultTyping.Demo2ObjectAndNonConcrete;
public interface Sex {
public void setSex(int sex);
public int getSex();
}
MySex.java
package com.DemoJackson.Study.DemoDefaultTyping.Demo2ObjectAndNonConcrete;
public class MySex implements Sex {
int sex;
@Override
public int getSex() {
return sex;
}
@Override
public void setSex(int sex) {
this.sex = sex;
}
}
JSTest.java
// OBJECT_AND_NON_CONCRETE:除了前面提到的特征,当类里有Interface、AbstractClass类时,对其进行序列化和反序列化(当然这些类本身需要时合法的、可被序列化的对象)。
// enableDefaultTyping()默认的无参数的设置就是此选项。
package com.DemoJackson.Study.DemoDefaultTyping.Demo2ObjectAndNonConcrete;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.age = 6;
p.name = "mi1k7ea";
p.object = new Hacker();
p.sex = new MySex();
// 设置OBJECT_AND_NON_CONCRETE
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
//mapper.enableDefaultTyping(); // 或直接无参调用,输出一样
String json = mapper.writeValueAsString(p);
System.out.println(json);
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
可以看到接口Interface也能够序列化和反序列化
1.3. NON_CONCRETE_AND_ARRAYS
NON_CONCRETE_AND_ARRAYS:除了前面提到的特征外,还支持Array类型。
JSTest.java
// 对Array类型成功进行了序列化和反序列化
// 类名变成了 [L类名
package com.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.age = 6;
p.name = "mi1k7ea";
p.sex = new MySex();
// object属性是Hacker类的数组
Hacker[] hackers = new Hacker[2];
hackers[0] = new Hacker();
hackers[1] = new Hacker();
p.object = hackers;
// 设置NON_CONCRETE_AND_ARRAYS
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
String json = mapper.writeValueAsString(p);
System.out.println(json);
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
结果: 数组的类名变成了 [L类名
[Lcom.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.Hacker
{"age":6,"name":"mi1k7ea","object":["[Lcom.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.Hacker;",[{"skill":"Jackson"},{"skill":"Jackson"}]],"sex":["com.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.MySex",{"sex":0}]}
Person.age=6, Person.name=mi1k7ea, [Lcom.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.Hacker;@6093dd95, com.DemoJackson.Study.DemoDefaultTyping.Demo3NonCconcreteAndArrays.MySex@5622fdf
1.4. NON_FINAL
NON_FINAL:除了前面的所有特征外,包含即将被序列化的类里的全部、非final的属性,也就是相当于整个类、除final外的属性信息都需要被序列化和反序列化。
Person.java
package com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal;
import com.DemoJackson.Study.DemoDefaultTyping.Demo2ObjectAndNonConcrete.Sex;
public class Person {
public int age;
public String name;
public Object object;
public Sex sex;
public Hacker hacker;
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s, %s, %s, %s", age, name, object == null ? "null" : object, sex == null ? "null" : sex, hacker == null ? "null" : hacker);
}
}
JSTest.java
// 对非final的hacker属性进行序列化和反序列化
package com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.age = 6;
p.name = "mi1k7ea";
p.object = new Hacker();
p.sex = new MySex();
p.hacker = new Hacker();
// 设置NON_FINAL
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String json = mapper.writeValueAsString(p);
System.out.println(json);
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
结果:可以看到序列化和反序列化了hacker属性,是因为设置了NON_FINAL属性,所以Person类的Hacker属性是Hacker类也能序列化和反序列化。
["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Person",{"age":6,"name":"mi1k7ea","object":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker",{"skill":"Jackson"}],"sex":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.MySex",{"sex":0}],"hacker":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker",{"skill":"Jackson"}]}]
Person.age=6, Person.name=mi1k7ea, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker@3ffc5af1, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.MySex@5e5792a0, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker@26653222
如果不设置NON_FINAL属性
结果:可以看到Person类的Hacker属性是不被序列化的
{"age":6,"name":"mi1k7ea","object":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker",{"skill":"Jackson"}],"sex":["com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.MySex",{"sex":0}],"hacker":{"skill":"Jackson"}}
Person.age=6, Person.name=mi1k7ea, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker@3ffc5af1, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.MySex@5e5792a0, com.DemoJackson.Study.DemoDefaultTyping.Demo4NonFinal.Hacker@26653222
2.@JsonTypeInfo注解
@JsonTypeInfo注解是Jackson多态类型绑定的一种方式,支持下面5种类型的取值:
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM)
1.NONE
不对Person类的object属性序列化和反序列化
Person.java
package com.DemoJackson.Study.DemoJsonTypeInfo.Demo1None;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
public class Person {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public Object object;
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
}
}
JSTest.java
// 和没有设置值为JsonTypeInfo.Id.NONE的@JsonTypeInfo注解是一样的
package com.DemoJackson.Study.DemoJsonTypeInfo.Demo1None;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.age = 6;
p.name = "mi1k7ea";
p.object = new Hacker();
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(p);
System.out.println(json);
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
Person类的object属性并没有序列化和反序列化
2.CLASS
Person.java
package com.DemoJackson.Study.DemoJsonTypeInfo.Demo2CLASS;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
public class Person {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public Object object;
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
}
}
object属性中多了”@class”:”xxxxxxx.Hacker”,说明对object属性的值序列化和反序列化了。
那么也就是说,在Jackson反序列化的时候如果使用了JsonTypeInfo.Id.CLASS
修饰的话,可以通过@class的方式指定相关类,并进行相关调用。
3.MINIMAL_CLASS
Person.java
package com.DemoJackson.Study.DemoJsonTypeInfo.Demo3MinimalClass;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
public class Person {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public Object object;
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
}
}
结果:object属性中多了”@c”:”xxxxxxxxxxx.Hacker”,即使用@c替代料@class,官方描述中的意思是缩短了相关类名,实际效果和JsonTypeInfo.Id.CLASS类似
{"age":6,"name":"mi1k7ea","object":{"@c":"com.DemoJackson.Study.DemoJsonTypeInfo.Demo3MinimalClass.Hacker","skill":"Jackson"}}
Person.age=6, Person.name=mi1k7ea, com.DemoJackson.Study.DemoJsonTypeInfo.Demo3MinimalClass.Hacker@204f30ec
0x03 总结
满足下面三个条件之一即存在Jackson反序列化漏洞:
- 调用了ObjectMapper.enableDefaultTyping()函数;
- 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.CLASS的@JsonTypeInfo注解;
- 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.MINIMAL_CLASS的@JsonTypeInfo注解;
0x04 参考链接
https://www.mi1k7ea.com/2019/11/13/Jackson%E7%B3%BB%E5%88%97%E4%B8%80%E2%80%94%E2%80%94%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86