Jackson-databind 反序列化分析

Posted by kingkk on 2019-05-23

Jackson基础操作

主要功能就是在java类与json字符串中间进行序列化与反序列化的操作。

序列化与反序列化

java bean

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
public class Person {
private String name;
private Integer age;

public String getName() {
return name;
}

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

public Integer getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

序列化

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception{
Person p = new Person();
p.setAge(20);
p.setName("kingkk");

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(p);
System.out.println(json);
// {"name":"kingkk","age":20}
}

反序列化

1
2
3
4
5
6
ObjectMapper mapper = new ObjectMapper();

String json = "{\"name\":\"kingkk\",\"age\":20}";

System.out.println(mapper.readValue(json, Person.class));
// Person{name='kingkk', age=20}

这也就是最基础的json序列化与反序列化操作

当然,这样简单的demo是没有什么安全问题的,问题出在于一些可选配置上

DefaultTyping

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
public enum DefaultTyping {
/**
* This value means that only properties that have
* {@link java.lang.Object} as declared type (including
* generic types without explicit type) will use default
* typing.
*/
JAVA_LANG_OBJECT,

/**
* Value that means that default typing will be used for
* properties with declared type of {@link java.lang.Object}
* or an abstract type (abstract class or interface).
* Note that this does <b>not</b> include array types.
*<p>
* Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes.
*/
OBJECT_AND_NON_CONCRETE,

/**
* Value that means that default typing will be used for
* all types covered by {@link #OBJECT_AND_NON_CONCRETE}
* plus all array types for them.
*<p>
* Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes.
*/
NON_CONCRETE_AND_ARRAYS,

/**
* Value that means that default typing will be used for
* all non-final types, with exception of small number of
* "natural" types (String, Boolean, Integer, Double), which
* can be correctly inferred from JSON; as well as for
* all arrays of non-final types.
*<p>
* Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes.
*/
NON_FINAL
}

默认情况下,mapper.enableDefaultTyping()什么参数都不加时,会默认调用OBJECT_AND_NON_CONCRETE

1
2
3
public ObjectMapper enableDefaultTyping() {
return enableDefaultTyping(DefaultTyping.OBJECT_AND_NON_CONCRETE);
}

重点说下OBJECT_AND_NON_CONCRETE这个参数

考虑如下这一种情况,序列化的对象的成员变量中,数据类型是一个 抽象类/接口 (或者直接看代码来的易懂点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Person {
private String name;
private Integer age;
private Cls cls;

// getter setter toString
}
public class Cls1 {
private String name="cls1";

// getter setter toString
}

public abstract class Cls {
}

这时候对这个类进行反序列化的时候,可以看到是无法进行反序列化的

1
2
3
4
5
6
7
8
9
10
11
12
Person p = new Person();
p.setAge(20);
p.setName("kingkk");
p.setCls(new Cls1());

ObjectMapper mapper = new ObjectMapper();

String json = mapper.writeValueAsString(p);
System.out.println(json);
// {"name":"kingkk","age":20,"cls":{"name":"cls1"}}

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

大意就是说由于Cls这个变量是一个抽象类,所以反序列化失败。因为jackson也不知道要反序列化它的哪一个实现。

这时候就需要开启enableDefaultTyping,对数据类型进行指定

将上面的代码加一条mapper.enableDefaultTyping();,就可以看到反序列化成功了

1
2
3
4
5
6
7
8
9
10
11
12
13
Person p = new Person();
p.setAge(20);
p.setName("kingkk");
p.setCls(new Cls1());

ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String json = mapper.writeValueAsString(p);
System.out.println(json);
// {"name":"kingkk","age":20,"cls":["cls.Cls1",{"name":"cls1"}]}

System.out.println(mapper.readValue(json, Person.class));
// Person{name='kingkk', age=20, cls=Cls1{name='cls1'}}

比较两次序列化后的json字符串,可以看到,多了指定的数据类型。

1
2
未开启enableDefaultTyping // {"name":"kingkk","age":20,"cls":{"name":"cls1"}}
开启了enableDefaultTyping // {"name":"kingkk","age":20,"cls":["cls.Cls1",{"name":"cls1"}]}

jackson也就可以根据数据类型去自动反序列化 出对应的类

那假如有一个Cls2,将传入的json字符串变成{"name":"kingkk","age":20,"cls":["cls.Cls2",{"name":"cls2"}]}是否也可以反序列化出对应的类呢,显然是可以的。

修改下

1
2
3
4
5
6
7
8
String json = "{\"name\":\"kingkk\",\"age\":20,\"cls\":[\"cls.Cls1\",{\"name\":\"cls1\"}]}";
String json2 = "{\"name\":\"kingkk\",\"age\":20,\"cls\":[\"cls.Cls2\",{\"name\":\"cls2\"}]}";

System.out.println(mapper.readValue(json, Person.class));
//Person{name='kingkk', age=20, cls=Cls1{name='cls1'}}

System.out.println(mapper.readValue(json2, Person.class));
//Person{name='kingkk', age=20, cls=Cls2{name='cls2'}}

那假如将数据类型Cls改成Object呢?是否就可以反序列化出任意类?

修改下Person类型

1
2
3
4
5
6
7
public class Person {
private String name;
private Integer age;
private Object cls;

// getter setter toString
}

假设此时有一个存在危险函数的类

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

Vuln(){
System.out.println("init");
}

public String getCmd() {
System.out.println("get");
return cmd;
}

public void setCmd(String cmd) throws IOException {
System.out.println("set");
this.cmd = cmd;
Runtime.getRuntime().exec(cmd);
}
}

尝试去反序列化这个Vuln

1
2
3
4
5
6
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();

String json = "{\"name\":\"kingkk\",\"age\":20,\"cls\":[\"vuln.Vuln\",{\"cmd\":\"calc\"}]}";

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

可以看到是可以反序列化到任意类的,而且当setter或构造函数中存在危险函数时也会自动触发。

CVE-2017-7525

显然,实际代码中开发也几乎不可能写出那样不靠谱的Vuln类,也为了寻找一种更为通用的反序列化链,也就是所说的Gadget

CVE-2017-7525中就是利用JDK7u21的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl作为Gadget。

具体的触发流程我也没具体跟进了,总的原理和之前Vuln类中的原理类似。

触发的调节为

  • jackson databind 2.7.10 及2.8.9以下版本
  • 开启了enableDefaultTyping
  • 类中存在Object类型的成员变量,或者想要反序列化类的抽象类/接口?

在这篇文章中也了解到http://www.leadroyal.cn/?p=630

当java bean的字段上加上了

  • @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
  • @JsonTypeInfo(use = JsonTypeInfo.Id.MANIMAL_CLASS)

两个注解时,即使没有开启enableDefaultTyping也可以进行指定类的反序列化。亲测有效。

漏洞修复

至于漏洞的修补比较惊讶的是基于黑名单的修复,仅仅限制了可以反序列化的类,导致之前的Poc失效。

https://github.com/FasterXML/jackson-databind/commit/60d459cedcf079c6106ae7da2ac562bc32dcabe1

也就意味着只要找到新的Gadget就还是可以利用这个洞(咋感觉比之前Weblogic XMLDecoder的修复还要不靠谱!

References

http://www.leadroyal.cn/?p=594

http://www.leadroyal.cn/?p=616

http://www.leadroyal.cn/?p=630

https://github.com/vulhub/vulhub/tree/master/jackson/CVE-2017-7525