首页 > 其他分享 >Jackson反序列化漏洞研究

Jackson反序列化漏洞研究

时间:2023-11-06 22:11:28浏览次数:32  
标签:Jackson String 漏洞 jackson 序列化 com public ObjectMapper

一、Jackson序列化库使用简介

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

 

二、Jackson反序列化漏洞原理分析

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

 

三、真实CVE漏洞案例分析

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

 

 

标签:Jackson,String,漏洞,jackson,序列化,com,public,ObjectMapper
From: https://www.cnblogs.com/LittleHann/p/17811918.html

相关文章

  • 【漏洞复现】瑞友天翼应用虚拟化系统RCE漏洞
    免责申明本公众号的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。本文......
  • XStream反序列化漏洞原理分析
    一、XStream简介0x1:XStream介绍Xstream是一种OXMapping技术,是用来处理XML文件序列化的框架,在将JavaBean序列化,或将XML文件反序列化的时候,不需要其它辅助类和映射文件,使得XML序列化不再繁索。Xstream也可以将JavaBean序列化成Json或反序列化,使用非常方便。使用方便-XStream......
  • Kafka反序列化RCE漏洞(CVE-2023-34040)
    漏洞描述SpringKafka是SpringFramework生态系统中的一个模块,用于简化在Spring应用程序中集成ApacheKafka的过程,记录(record)指Kafka消息中的一条记录。受影响版本中默认未对记录配置 ErrorHandlingDeserializer,当用户将容器属性 checkDeserExWhenKeyNull 或 chec......
  • python 自定义序列化器
    @Serialization是一个自定义装饰器,通常用于序列化Python对象。使用@Serialization装饰器可以将一个类转换为可序列化的对象,这样就可以将其存储到文件或通过网络传输。下面是一个使用@Serialization装饰器的示例:importjsondefSerialization(cls):defserialize(......
  • 棱镜七彩兼容CCF版开源漏洞信息描述规范COSV Schema 1.0
    CCF版开源漏洞信息描述规范COSVSchema1.0(以下简称“COSVSchema1.0”)已于前期正式发布,棱镜七彩作为COSVSchema1.0制定工作的重要成员积极响应规范内容,目前公司产品与漏洞推送服务已经实现COSVSchema1.0兼容。开源软件在现代软件开发中占据着重要作用,因此开源软件中存在的开源......
  • 爱数文档云zabbix默认密码漏洞
    1.访问 http://ovip:10049/zabbix,登录Admin账户。默认密码为zabbix。且修改后会导致系统异常。2.点击监控主页右上角的账户按钮,查看当前账户配置。3.点击Changepassword按钮,修改账户密码信息。4.输入Admin账户默认密码zabbix,点击update更新。参考链接:https://www.aish......
  • 反序列化
    反序列化反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)存在用户可控;通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性例子:<?phpclasstest{public$a='echo"thisistest";';//用户传入参publicfunctiondispalyVar(){eval($t......
  • jackson序列化key排序
    对象在序列化的时候对key进行排序使用 JsonPropertyOrder```java@Target({ElementType.ANNOTATION_TYPE,ElementType.TYPE,ElementType.METHOD,ElementType.CONSTRUCTOR,ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@JacksonAnnotationpublic@interface......
  • 【漏洞复现】大唐电信AC集中管理平台敏感信息泄漏漏洞
    免责申明本公众号的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。本文......
  • 【宝藏工具】开源组件信息一键查询,快速获取组件来源、版本、源码地址、漏洞补丁、推荐
    铁子们,分享一个开源组件安全检索免费工具,需要的自取~输入组件名,一键查询可以组件版本、来源、安全状态、漏洞详情和推荐版本、修复建议这些。点这个链接注册后直接就能用:组件安全检索工具 一键查询第三方组件版本、漏洞、所属国家、所属语言、源码链接等:查看漏洞详情:......