Jackson反序列化漏洞研究 - 郑瀚Andrew
2023-11-6 22:6:0 Author: www.cnblogs.com(查看原文) 阅读量:19 收藏

0x1:Jackson背景

Jackson是一个强大而高效的Java库,处理Java对象及其JSON表示的序列化和反序列化。它是这项任务中使用最广泛的库之一,并在许多其他框架中作为默认的Json引擎使用。例如,虽然Spring框架支持各种序列化/反序列化库,但Jackson是默认引擎。

Jackson 功能很强大,既能满足简单的序列化和反序列化操作,也能实现复杂的、个性化的序列化和反序列化操作。到目前为止,Jackson 的序列化和反序列化性能都非常优秀,已经是国内外大部分 JSON 相关编程的首选工具。

Jackson从 2.0 开始改用新的包名 fasterxml,1.x 版本的包名是 codehaus。除了包名不同,他们的 Maven artifact id 也不同。1.x 版本现在只提供 bug-fix,而 2.x 版本还在不断开发和发布中。如果是新项目,建议直接用 2x,即 fasterxml jackson。

0x2:Jackson操作 

1、ObjectMapper类

Jackson库中用于读写JSON的主要类是ObjectMapper,它在com.fasterxml.jackson.databind 包中,可以序列化和反序列化两种类型的对象。

  • 普通的旧Java对象(POJO)
  • 通用的JSON树模型

ObjectMapper 类提供了四个构造函数来创建一个实例,

2、将JSON转换为Java对象

最简单的输入形式是String,或者说,JSON格式的字符串。 

考虑一个健康管理系统中的以下 HealthWorker 类。

package com.example.jackson_test;

public class HealthWorker {
    private int id;
    private String name;
    private String qualification;
    private Double yearsOfExperience;

    // Constructor, getters, setters, toString()

    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }

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

    public void setQualification(String qualification) {
        this.qualification = qualification;
    }
    public String getQualification() {
        return qualification;
    }

    public void setYearsOfExperience(Double yearsOfExperience) {
        this.yearsOfExperience = yearsOfExperience;
    }
    public Double getYearsOfExperience() {
        return yearsOfExperience;
    }
}

要把这个类的JSON字符串表示法转换成一个Java类,我们只需把这个字符串提供给readValue() 方法,同时提供我们要转换的类的.class 。 

package com.example.jackson_test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JacksonTestApplication {

    public static void main(String[] args) throws JsonProcessingException {

        //SpringApplication.run(JacksonTestApplication.class, args);

        ObjectMapper objectMapper = new ObjectMapper();
        String healthWorkerJSON = "{\"id\":1,\"name\":\"RehamMuzzamil\",\"qualification\":\"MBBS\",\"yearsOfExperience\":1.5}";

        HealthWorker healthWorker = objectMapper.readValue(healthWorkerJSON, HealthWorker.class);
        System.out.println(healthWorker.getId());
        System.out.println(healthWorker.getName());
        System.out.println(healthWorker.getQualification());
        System.out.println(healthWorker.getYearsOfExperience());
    }

}

注意:字段名必须与JSON字符串中的字段完全匹配,否则映射器会抛出一个错误。此外,它们必须有有效的公共getters和setters。Jackson还支持使用不同名称的别名,可以通过简单的注释将任何JSON字段映射到任何POJO字段。 

3、Jackson常用注解 

Jackson包含了很多注解,用来个性化序列化和反序列化操作,主要包含如下注解。 

  • @JsonProperty,作用在属性上,用来为 JSON Key 指定一个别名;
  • @JsonIgnore,作用在属性上,用来忽略此属性;
  • @JsonIgnoreProperties,忽略一组属性,作用在类上,比如 @JsonIgnorePropertiess({"id","phone"});
  • @JsonAnySetter,标记在某个方法上,此方法接收 Key、Value,用于 Jackson 在反序列化过程中,未找到的对应属性都调用此方法。通常这个方法用一个 Map 来实现;
  • @JsonAnyGetter,此注解标注在一个返回 Map 的方法上,Jackson 会取出 Map中的每一个值进行序列化;
  • @JsonFormat,用于日期的格式化
  • @JsonNaming,用于指定一个命名策略,作用于类或者属性上,类似 @JsonProperty,但是会自动命名。Jackson 自带了多种命名策略,你也可以实现自己的命名策略,比如输入的 Key 由 Java 命名方式转换为下划线命名方式:userName 转换为 user-name;
  • @JsonSerialize,指定一个实现类来自定义序列化。类必须实现 JsonSerializer 接口;
  • @JsonDeserialize,用户自定义反序列化,同 @JsonSerialize,类需要实现 JsonDeserialize 接口;
  • @JsonView,作用在类或者属性上,用来定义一个序列化组。Spring MVC 的 Controller 方法可以使用同样的 @JsonView 来序列化属于这一组的配置。

4、@JsonAlias 和 @JsonProperty

每当JSON字符串和POJO中的属性/字段名称不匹配时,你可以通过不反序列化它们,或 "调整 "哪些JSON字段被映射到哪些对象字段来处理这种不匹配。

这可以通过@JsonAlias 和@JsonProperty 来实现。

  • @JsonProperty对应于序列化和反序列化过程中的字段名
  • @JsonAlias对应于反序列化过程中的替代名称

例如,一个常见的不匹配发生在大写字母的约定上:一个API可能会返回snake_case,而你期待的是CamelCase。

HealthWorker.java

package com.example.jackson_test;

public class HealthWorker {
    private int id;
    private String name;
    private String qualification;
    private Double yearsOfExperience;

    // Constructor, getters, setters, toString()

    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }

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

    public void setQualification(String qualification) {
        this.qualification = qualification;
    }
    public String getQualification() {
        return qualification;
    }

    public void setYearsOfExperience(Double yearsOfExperience) {
        this.yearsOfExperience = yearsOfExperience;
    }
    public Double getYearsOfExperience() {
        return yearsOfExperience;
    }
}

而传入的JSON看起来像这样,

{
  "id" : 1,
  "name" : "RehamMuzzamil",
  "qualification" : "MBBS",
  "years_of_experience" :1.5
}

上面JSON中,“years_of_experience”是不被认可的字段,尽管它们显然代表相同的属性。

我们可以通过设置 @JsonProperty 注解来避免这种情况。

此外,你可以同时使用这两个注解,通过设置 @JsonAlias,除了.class中声明的属性名之外,别名列表中的别名也同样可以被传入并接受。

package com.example.jackson_test;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;

public class HealthWorker {
    private int id;
    private String name;
    private String qualification;
    @JsonProperty("years_of_experience")
    @JsonAlias({"yoe", "yearsOfExperience", "experience"})
    private Double yearsOfExperience;

    // Constructor, getters, setters, toString()

    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }

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

    public void setQualification(String qualification) {
        this.qualification = qualification;
    }
    public String getQualification() {
        return qualification;
    }

    public void setYearsOfExperience(Double yearsOfExperience) {
        this.yearsOfExperience = yearsOfExperience;
    }
    public Double getYearsOfExperience() {
        return yearsOfExperience;
    }
}

5、从HTTP响应/URL将JSON转换为Java对象(POJO)

JSON被创建为一种数据交换格式,特别是用于网络应用,它是网络上最普遍的数据序列化格式。

虽然你可以检索结果,将其保存为一个字符串,然后使用readValue() 方法进行转换,但同时你也可以直接读取HTTP响应,给定一个URL,并将其反序列化为所需的类。通过这种方法,你可以跳过中间的String,直接解析HTTP请求的结果。

String API_KEY = "552xxxxxxxxxxxxxxxxx122&";
String URLString = "http://api.weatherapi.com/v1/astronomy.json?key="+API_KEY+"q=London&dt=2021-12-30\n";
URL url = new URL(URLString); // Create a URL object, don't just use a URL as a String
ObjectMapper objectMapper = new ObjectMapper();
Astronomy astronomy = objectMapper.readValue(url, Astronomy.class);

6、实现一个自定义的Jackson序列化器

创建一个实现JsonSerializer接口的自定义序列化器类。该接口有两个泛型参数,第一个参数是要序列化的类型,第二个参数是要序列化为的JSON类型。

在自定义序列化器类中实现serialize方法。在该方法中,您可以编写与输入对象的序列化逻辑。使用JsonGenerator参数可以将属性写入JSON输出。

package com.example.jackson_test;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class CustomSerializer extends JsonSerializer<HealthWorker> {

    @Override
    public void serialize(HealthWorker value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 在此处实现您的序列化逻辑
        gen.writeString(value.toString());
    }
}

注册自定义序列化器。您需要将自定义序列化器注册到ObjectMapper实例中,以便在序列化对象时使用它。

package com.example.jackson_test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.io.IOException;

@SpringBootApplication
public class JacksonTestApplication {

    public static void main(String[] args) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(HealthWorker.class, new CustomSerializer());
        objectMapper.registerModule(module);

        HealthWorker obj = new HealthWorker();
        String json = objectMapper.writeValueAsString(obj);
        System.out.println(json);
    }

}

用于存储反序列化的Java定义如下,

package com.example.jackson_test;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;

public class HealthWorker {
    private int id;
    private String name;
    private String qualification;
    @JsonProperty("years_of_experience")
    @JsonAlias({"yoe", "yearsOfExperience", "experience"})
    private Double yearsOfExperience;

    // Constructor, getters, setters, toString()

    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }

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

    public void setQualification(String qualification) {
        this.qualification = qualification;
    }
    public String getQualification() {
        return qualification;
    }

    public void setYearsOfExperience(Double yearsOfExperience) {
        this.yearsOfExperience = yearsOfExperience;
    }
    public Double getYearsOfExperience() {
        return yearsOfExperience;
    }
}

0x3:在Spring项目中使用Jackson

使用Spring Initializr通过GUI创建一个骨架项目。Jackson不是一个内置的依赖项,所以你不能通过CLI或Spring Initializr将其包括在内,不过,引入Jackson就像修改你的pom.xml 文件一样容易。

<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>

Jackson提供了ObjectMapper.writeValueAsString()和ObjectMapper.readValue()两个方法来实现序列化和反序列化的功能。

User.java

package com.example.jackson_test;

public class User {
    private String username;
    private String password;
    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

JacksonTest.java

package com.example.jackson_test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class JacksonTest {
    public static void main(String[] args) throws IOException {
        User user = new User("Sentiment","123456");
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(user);
        System.out.println(json);
        User other = mapper.readValue(json,User.class);
        System.out.println(other);
    }
}

另一个例子。

Person.java

package com.example.jackson_test;

class Person {
    public int age;
    public String name;

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

主程序,

package com.example.jackson_test;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.io.IOException;

@SpringBootApplication
public class JacksonTestApplication {

    public static void main(String[] args) throws IOException {

        Person p = new Person();
        p.age = 10;
        p.name = "Alice";

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
        Person p2 = mapper.readValue(json, Person.class);
        System.out.println(p2);
    }

}

Jackson的基础写法,是不存在漏洞的,但是Json规范里有很多额外自定义的“特性”,这些“特性”会引入额外的解析规则,同时也引入了额外的漏洞。

参考链接:

https://www.cnblogs.com/cnjavahome/p/8393178.html
https://juejin.cn/post/7114895114559815717

0x1:反序列化中的多态问题

和fastjson情况类似一样,在实际场景中常常需要解决多态的问题,比如:Apple是苹果还是苹果手机等,在fastjson中引入了@type字段来解决该问题。

而在Jackson中也有与之对应的方法,Jackson实现了JacksonPolymorphicDeserialization机制来解决这个问题,有两种方法:

  • DefaultTyping
  • @JsonTypeInfo注解

1、通过DefaultTyping的配置解决多态问题

jackson提供一个设置,叫enableDefaultTyping,有4个值,在com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping能看到

  • JAVA_LANG_OBJECT
  • OBJECT_AND_NON_CONCRETE
  • NON_CONCRETE_AND_ARRAYS
  • NON_FINAL

默认情况下,即无参数的enableDefaultTyping是第二个设置,OBJECT_AND_NON_CONCRETE。

总结一下这4个类型设置的区别,

DefaultTyping类型描述说明
JAVA_LANG_OBJECT 属性的类型为Object
OBJECT_AND_NON_CONCRETE 属性的类型为Object、Interface、AbstractClass
NON_CONCRETE_AND_ARRAYS 属性的类型为Object、Interface、AbstractClass、Array
NON_FINAL 所有除了声明为final之外的属性

1)JAVA_LANG_OBJECT 

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

例如下面的代码,我们给Person里添加一个Object object。

Dna.java

package com.example.jackson_test;

class Dna {
    public int length = 100;
}

Person.java

package com.example.jackson_test;

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);
    }
}

主程序,

package com.example.jackson_test;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.io.IOException;

@SpringBootApplication
public class JacksonTestApplication {

    public static void main(String[] args) throws IOException {
        Person p = new Person();
        p.age = 10;
        p.name = "Alice";
        p.object = new Dna();
        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);
    }

}

可以看到,当开启了EnableDefaultTyping之后,Json序列化后多出来了一些关于com.example.jackson_test.Dna的信息。将来进行反序列化的时候会进行相应的类重建还原。

2)OBJECT_AND_NON_CONCRETE

当类里有Interface 、AbstractClass时,需要对其进行序列化和反序列化,可以指定OBJECT_AND_NON_CONCRETE。

Sex.java

package com.example.jackson_test;

interface Sex {
    public void setSex(int sex);

    public int getSex();
}

MySex.java

package com.example.jackson_test;

class MySex implements Sex {
    int sex;

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

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

Dna.java

package com.example.jackson_test;

class Dna {
    public int length = 100;
}

Person.java

package com.example.jackson_test;

class Person {
    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);
    }
}

主程序,

package com.example.jackson_test;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.io.IOException;

@SpringBootApplication
public class JacksonTestApplication {

    public static void main(String[] args) throws IOException {
        Person p = new Person();
        p.age = 10;
        p.name = "Alice";
        p.object = new Dna();
        p.sex = new MySex();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
        Person p2 = mapper.readValue(json, Person.class);
        System.out.println(p2);
    }

}

3)NON_CONCRETE_AND_ARRAYS

除了上文提到的特征,还支持上文全部类型的Array类型。

4)NON_FINAL

包括上文提到的所有特征,而且包含即将被序列化的类里的全部、非final的属性,也就是相当于整个类、除final外的的属性信息都需要被序列化和反序列化。

Sex.java、MySex.java、Dna.java、Person.java和之前一样,

package com.example.jackson_test;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.io.IOException;

@SpringBootApplication
public class JacksonTestApplication {

    public static void main(String[] args) throws IOException {
        Person p = new Person();
        p.age = 10;
        p.name = "Alice";
        p.object = new Dna();
        p.sex = new MySex();
        p.dna = new Dna();
        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);


    }

}

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)

0x2:序列化的基本逻辑

User.java

package com.example.jackson_test;

public class User {
    private String username;
    private String password;
    private Object object;
    public User() {
    }

    public User(String username, String password, Object object) {
        this.username = username;
        this.password = password;
        this.object = object;
    }

    public String getUsername() {
        return username;
    }
    public String getPassword() {
        return password;
    }
    public Object getObject() {
        return object;
    }

    public void setUsername(String username) {
        this.username = username;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void setObject(Object object) {
        this.object = object;
    }
}

User2.java

package com.example.jackson_test;

public class User2 {
    public String name="Tana";
}

JacksonTest.java

package com.example.jackson_test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class JacksonTest {
    public static void main(String[] args) throws IOException {
        User user = new User("Sentiment","123456", new User2());
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        String json = mapper.writeValueAsString(user);
        System.out.println(json);
        User other = mapper.readValue(json,User.class);
        System.out.println(other);
    }
}

常规情况下,Jackson在序列化时候,要求field可以访问到,先走getter,如果没有的话就去找field,如果还没有,就跳过这个field。

  • 在没有开启defaultTyping的情况下,所有的field都要是基本数据类型、数组、集合类型和由常见类型组成的类等常见的数据,一旦属性里包含了非基本数据类型,就直接抛异常了。
  • 在开启了 dafaultTyping的情况下,同样会对所有field都做一遍序列化。但区别在于,对不常见的类型反序列化时,需要主动指明类名,所以结构会不一样。

也就是说,开启和关闭 defaultTyping 二者是相互无法处理对方的字符串的,因为协议不一样,就拿上面这个例子,开启前后序列化出来的内容如下: 

// 关闭 defaultTyping
{"username":"Sentiment","password":"123456","object":{"name":"Tana"}}
com.example.jackson_test.User@15d0c81b

// 开启 defaultTyping
["com.example.jackson_test.User",{"username":"Sentiment","password":"123456","object":["com.example.jackson_test.User2",{"name":"Tana"}]}]
com.example.jackson_test.User@1a1d6a08

换句话说,在关闭defaultTyping情况,因为只能序列化和反序列化常规的基本数据类型,所以根本不存在Java对象注入攻击面,而当开启了defaultTyping后,Jackson理论上就打开了一个“Java任意对象反序列化注入”的攻击面。

0x3:反序列化的基本逻辑

首先对于不开启defaultTyping的,根据json里的key-value中的key,去找对应变量的setter,找不到的话就找对应变量,还找不到的话,如果没有设置 ignore unknown,就抛异常了。因为控制的都是基本类型,所以攻击者无法发动攻击,这是一种保守、安全的配置。

但是如果开启了defaultTyping,就不一样了,这里存在两种攻击面,

  • 第一种格式是类名后面加一堆key-value,去反序列化指定的类
  • 第二种是类名后面跟一个参数,但必须是基本类型,官方解释是方便使用,一般推荐String,这样就可以从String反序列化这个类了

接下来我们通过debug调试源码,深入了解一下jackson反序列的过程。代码和上一节一致。

该流程分析是对使用DefaultTyping方法的分析,但是用@JsonTypeInfo跟这个是一模一样的。

跟fastjson类似,反序列化时会调用对应类的setter和无参构造器,而由于object属性是User2类型的,这里会分别调用User类和User2类的无参构造,

根据一级级调用,到了vanillaDeserialize()中,先调用createUsingDefault()函数来调用指定类的无参构造函数来生成类实例:

跟进,又调用了__call()方法,

跟进call(),通过newInstance进行了实例化,也就是将User类进行了实例化,

继续跟进,因为User被实例化了所以调用了User类的无参构造,

接着回到vanillaDeserialize(),生成实例Bean后,就开始进入do while循环来循环解析键值对中的属性值并调用deserializeAndSet()函数来解析并设置Bean的属性值: 

跟进后先调用了deserialize(), 

继续跟进,这里if会判断,反序列化内容是否携带类型,这里第一个属性是String类型所以调用到了第二个分支里, 

跟进deserialize()后,嗲用了p.getText(),获取到了p的属性值。

最后通过invoke命令调用到了username的setter方法。

password属性也是String类型的,所以通过前边的do while循环,在调用deserializeAndSet();获取对应的值,跟username过程完全一样。 

继续跟踪User2类的反序列化。

获取到password的值后,接着进入下一轮循环调用deserializeAndSet(),接着调用deserialize()判断反序列化数据的携带类型,由于User类中的object属性值是user2类的实例化,所以它是一个数组类型调用到了deserializeWithType()这里,

跟进后又调用了deserializeTypedFromAny(),又调用_deserialize(),跟进后获取了deser的类型为BeanDeserializer,接着调用了该类的deserialize()。

跟进后获取了deser的类型为BeanDeserializer,接着调用了该类的deserialize()。

接着就回到了流程分析最开始的部分,调用vanillaDeserialize(),

之后就是分别调用User2的构造器或Setter方法。

0x4:反序列化中存在的漏洞攻击面

1、前提条件

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

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

2、漏洞场景

1)属性不为Object类时

当要进行反序列化的类的属性所属类的构造函数或setter方法本身存在漏洞时,这种场景存在Jackson反序列化漏洞。当然这种场景开发几乎不会这么写,除非程序员自己想留后门。

2)属性为Object类时

当属性类型为Object时,因为Object类型是任意类型的父类,因此扩大了我们的攻击面,我们只需要寻找出在目标服务端环境中存在的且构造函数或setter方法存在漏洞代码的类即可进行攻击利用。后面出现的Jackson反序列化的CVE漏洞、黑名单绕过等都是基于这个原理寻找各种符合条件的利用链。

这里我们假设目标服务端环境中存在其一个恶意类User2,其setter方法存在任意代码执行漏洞,存在于com.example.jackson_test包中。

User.java

package com.example.jackson_test;

public class User {
    private String username;
    private String password;
    private Object object;
    public User() {
    }

    public User(String username, String password, Object object) {
        this.username = username;
        this.password = password;
        this.object = object;
    }

    public String getUsername() {
        return username;
    }
    public String getPassword() {
        return password;
    }
    public Object getObject() {
        return object;
    }

    public void setUsername(String username) {
        this.username = username;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void setObject(Object object) {
        this.object = object;
    }
}

User2.java

package com.example.jackson_test;

public class User2 {
    public String name="Tana";

    public User2() {
        System.out.println("User2无参构造");
    }

    public void setName(String name) {
        System.out.println("User2.setName");
        this.name = name;
        try {
            Runtime.getRuntime().exec("open -a Calculator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JacksonTest.java

package com.example.jackson_test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class JacksonTest {
    public static void main(String[] args) throws IOException {
        User user = new User("Sentiment","123456", new User2());
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        String json = mapper.writeValueAsString(user);
        System.out.println(json);
        User other = mapper.readValue(json,User.class);
        System.out.println(other);
    }
}

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

参考链接:

https://blog.csdn.net/caiqiiqi/article/details/100930192 
https://xz.aliyun.com/t/12628
https://www.leadroyal.cn/p/630/ 
https://www.leadroyal.cn/p/594/ 
http://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/ 
https://github.com/JoyChou93/java-sec-code

0x1:CVE-2017-7525(基于TemplatesImpl利用链)

0x2:CVE-2017-17485(基于ClassPathXmlApplicationContext利用链)

0x3:CVE-2019-12086(基于MiniAdmin利用链)

0x4:CVE-2019-12814(基于JDOM XSLTransformer利用链)

参考链接:

http://www.mi1k7ea.com/2019/11/16/Jackson%E7%B3%BB%E5%88%97%E4%BA%8C%E2%80%94%E2%80%94CVE-2017-7525%EF%BC%88%E5%9F%BA%E4%BA%8ETemplatesImpl%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/
http://www.mi1k7ea.com/2019/11/17/Jackson%E7%B3%BB%E5%88%97%E4%B8%89%E2%80%94CVE-2017-1748%EF%BC%88%E5%9F%BA%E4%BA%8EClassPathXmlApplicationContext%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/
http://www.mi1k7ea.com/2019/11/19/Jackson%E7%B3%BB%E5%88%97%E5%9B%9B%E2%80%94%E2%80%94CVE-2019-12086%EF%BC%88%E5%9F%BA%E4%BA%8EMiniAdmin%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/
http://www.mi1k7ea.com/2019/11/22/Jackson%E7%B3%BB%E5%88%97%E4%BA%94%E2%80%94%E2%80%94CVE-2019-12384%EF%BC%88%E5%9F%BA%E4%BA%8Elogback%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/
http://www.mi1k7ea.com/2019/11/24/Jackson%E7%B3%BB%E5%88%97%E5%85%AD%E2%80%94%E2%80%94CVE-2019-12814%EF%BC%88%E5%9F%BA%E4%BA%8EJDOM-XSLTransformer%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%89/
http://www.mi1k7ea.com/2019/11/24/Jackson%E7%B3%BB%E5%88%97%E4%B8%83%E2%80%94%E2%80%94%E5%85%B6%E4%BB%96Gadgets/ 

0x1:白盒检测方法

第一个特征是关于EnableDefaultTyping 的,注意他的多个重载方法,共有4个。

ObjectMapper enableDefaultTyping()
ObjectMapper enableDefaultTyping(DefaultTyping dti)
ObjectMapper enableDefaultTyping(DefaultTyping applicability, JsonTypeInfo.As includeAs)
ObjectMapper enableDefaultTypingAsProperty(DefaultTyping applicability, String propertyName)

只要匹配到,就算命中了条件A。

第二个特征是在 readValue 时候,指定的类本身是Object或者里面一定要包含Object类型字段或者Object类型的setter。

public <T> T readValue(String content, JavaType valueType)
public <T> T readValue(Reader src, Class<T> valueType)
public <T> T readValue(Reader src, TypeReference valueTypeRef)
public <T> T readValue(Reader src, JavaType valueType)

所以只要匹配到这个,就需要对第二个参数进行解析,确认这个Model是否是可以被攻击的Model。如果包含了Object或者本身就是个Object,就认为命中了条件B。

第三个特征是被反序列的类里面,有被 JsonTypeInfo 注解过的类,而且里面的内容是 JsonTypeInfo.Id.CLASS 或 JsonTypeInfo.Id.MINIMAL_CLASS 。记做条件C。
最终条件就是:

(A&&B) || C

0x2:防御方法

将对应的gadget添加到黑名单,一旦反序列化到黑名单的类,就直接终止,抛异常,防止发生危害。

参考链接:

https://github.com/shadowsock5/jackson-databind-POC/blob/master/Jackson.java
https://www.leadroyal.cn/p/633/
https://github.com/shadowsock5/jackson-databind-POC#%E7%AC%AC%E4%BA%8C%E4%B8%AA%E7%89%B9%E5%BE%81%E6%98%AF%E5%9C%A8-readvalue-%E6%97%B6%E5%80%99%E6%8C%87%E5%AE%9A%E7%9A%84%E7%B1%BB%E6%9C%AC%E8%BA%AB%E6%98%AFobject%E6%88%96%E8%80%85%E9%87%8C%E9%9D%A2%E4%B8%80%E5%AE%9A%E8%A6%81%E5%8C%85%E5%90%ABobject%E7%B1%BB%E5%9E%8B%E5%AD%97%E6%AE%B5%E6%88%96%E8%80%85object%E7%B1%BB%E5%9E%8B%E7%9A%84setter

文章来源: https://www.cnblogs.com/LittleHann/p/17811918.html
如有侵权请联系:admin#unsafe.sh