java反序列化漏洞及其檢測(cè)
1 java反序列化簡(jiǎn)介
java反序列化是近些年安全業(yè)界研究的重點(diǎn)領(lǐng)域之一,在Apache Commons Collections、JBoss、WebLogic 等常見(jiàn)容器、庫(kù)中均發(fā)現(xiàn)有該類(lèi)漏洞,而且該類(lèi)型漏洞容易利用,造成的破壞很大,因此影響廣泛。
在本文中將先介紹java反序列化漏洞的原理,然后在此基礎(chǔ)上介紹安全工具如何檢測(cè)、掃描此類(lèi)漏洞。
1.1 什么是反序列化
Java序列化是指把Java對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程,序列化后的字節(jié)數(shù)據(jù)可以保存在文件、數(shù)據(jù)庫(kù)中;而Java反序列化是指把字節(jié)序列恢復(fù)為Java對(duì)象的過(guò)程。如下圖所示
?
?

序列化和反序列化通過(guò)ObjectInputStream.readObject()
和ObjectOutputStream.writeObject()
方法實(shí)現(xiàn)。
在java中任何類(lèi)如果想要序列化必須實(shí)現(xiàn)java.io.Serializable
接口,例如:
public
class
Hello
implements
java.io.Serializable
{String name
;}
java.io.Serializable
其實(shí)是一個(gè)空接口,在java中該接口的唯一作用是對(duì)一個(gè)類(lèi)做一個(gè)標(biāo)記讓jre確定這個(gè)類(lèi)是可以序列化的。
同時(shí)java中支持在類(lèi)中定義如下函數(shù):
private
void
writeObject(java.io.ObjectOutputStream out
)??????
throws
IOExceptionprivate
void
readObject(java.io.ObjectInputStream in
)??????
throws
IOException,
ClassNotFoundException;
這兩個(gè)函數(shù)不是java.io.Serializable
的接口函數(shù),而是約定的函數(shù),如果一個(gè)類(lèi)實(shí)現(xiàn)了這兩個(gè)函數(shù),那么在序列化和反序列化的時(shí)候ObjectInputStream.readObject()
和ObjectOutputStream.writeObject()
會(huì)主動(dòng)調(diào)用這兩個(gè)函數(shù)。這也是反序列化產(chǎn)生的根本原因
例如:
public
class
Hello
implements
java.io.Serializable
{???
String name
;private
void
readObject(java.io.ObjectInputStream in
)
throws
IOException,
ClassNotFoundException
{???????
Runtime.getRuntime().exec(name
);}}
該類(lèi)在反序列化的時(shí)候會(huì)執(zhí)行命令,我們構(gòu)造一個(gè)序列化的對(duì)象,name為惡意命令,那么在反序列化的時(shí)候就會(huì)執(zhí)行惡意命令。
在反序列化的過(guò)程中,攻擊者僅能夠控制“數(shù)據(jù)”,無(wú)法控制如何執(zhí)行,因此必須借助被攻擊應(yīng)用中的具體場(chǎng)景來(lái)實(shí)現(xiàn)攻擊目的,例如上例中存在一個(gè)執(zhí)行命令的可以序列化的類(lèi)(Hello),利用該類(lèi)的readObject函數(shù)中的命令執(zhí)行場(chǎng)景來(lái)實(shí)現(xiàn)攻擊。
1.2 反序列化漏洞示例復(fù)現(xiàn)
在這里我們構(gòu)造一個(gè)有漏洞的靶場(chǎng)進(jìn)行漏洞復(fù)現(xiàn)測(cè)試:使用spring-boot編寫(xiě)一個(gè)可以接收http數(shù)據(jù)并反序列化的應(yīng)用程序。
使用https://start.spring.io/生成一個(gè)spring-boot應(yīng)用,選擇Maven Project、java8
?

?
下載到本地,導(dǎo)入IDE,修改pom.xml加入Apache Commons Collections 3.1依賴(lài)(該版本存在反序列化漏洞)
<dependency>?
<groupId>commons-collections
</groupId>?
<artifactId>commons-collections
</artifactId>?
<version>3.1
</version></dependency>
修改DemoApplication.java 為如下代碼
?
?

?
?
此時(shí)我們就完成了一個(gè)有Apache Commons Collections漏洞的驗(yàn)證靶場(chǎng),啟動(dòng)該靶場(chǎng)應(yīng)用
我們使用 ysoserial 生成攻擊payload:
java -jar ysoserial-master-8eb5cbfbf6-1.jar CommonsCollections5
"calc.exe"
> poc
然后使用 httpie 發(fā)送攻擊payload(poc)
http post http://127.0.0.1:8080/rmi
< poc
這時(shí)候就可以看到poc中的命令執(zhí)行了
?

1.3 反序列化漏洞解析
在1.2 的示例中我們使用了 ysoserial 的 CommonsCollections5
這個(gè)payload,本節(jié)我們對(duì)此poc進(jìn)行分析
?

?
可以最終反序列化的對(duì)象為 javax.management.BadAttributeValueExpException
,在該類(lèi)提供了 readObject 方法,在其中有問(wèn)題的地方為
val
= valObj
.toString();
這里的 valObj 為 TiedMapEntry(lazyMap, “foo”) ,該類(lèi)的toString方法
public
String
toString()
{return
this.getKey()
+
"="
+
this.getValue();}
其中 this.getValue 為
public
Object
getValue()
{return
this.map
.get(this.key
);}
而 this.map 為 lazyMap = LazyMap.decorate(innerMap, transformerChain),在 lazyMap 中
public
Object
get(Object key
)
{???
if
(!super.map
.containsKey(key
))
{?
// 當(dāng)找不到key的時(shí)候調(diào)用transform???????
Object value
=
this.factory
.transform(key
);???????
super.map
.put(key
, value
);???????
return value
;???
}
else
{???????
return
super.map
.get(key
);}}
在其中看到,沒(méi)有找到key的時(shí)候,調(diào)用了 this.factory.transform(key)
而this.factory為我們構(gòu)造的包含payload的執(zhí)行鏈 transformerChain 該transformer會(huì)最終通過(guò)反射執(zhí)行命令。
2 java反序列化漏洞檢測(cè)
在1中的原理介紹中,我們可以看到,反序列化漏洞需要依賴(lài)執(zhí)行鏈來(lái)完成攻擊payload執(zhí)行。由于反序列化漏洞的特性,在檢測(cè)的時(shí)候漏洞掃描工具一般聚焦已知漏洞的檢測(cè),而未知漏洞的檢測(cè),安全工具能力非常有限,一般需要專(zhuān)業(yè)人員通過(guò)安全審計(jì)、代碼審計(jì)等方式發(fā)現(xiàn)。
java反序列化漏洞依賴(lài)于兩個(gè)因素:
1.應(yīng)用是否有反序列化接口
2.應(yīng)用中是否包含有漏洞的組件
因此對(duì)應(yīng)的漏洞掃描工具也需要根據(jù)這兩個(gè)因素進(jìn)行檢測(cè)。
2.1 白盒工具檢測(cè)
白盒代碼審計(jì)工具,可通過(guò)在調(diào)用鏈中查找是否有發(fā)序列化的操作:
·??????? 調(diào)用鏈的入口不同框架是不同的,例如在1.2例子中調(diào)用鏈的入口為spring-boot的controller。
·??????? 調(diào)用鏈中一旦發(fā)現(xiàn)有發(fā)序列化操作ObjectInputStream.readObject()
則該接口存在序列化操作
但僅僅依靠以上信息不足以判斷是否存在漏洞,還需要判斷代碼中是否有存在*執(zhí)行鏈**的三方依賴(lài)。在java中,一般通過(guò)分析 pox.xml
build.gradle
文件來(lái)分析是否包含有漏洞的組件。
2.2 黑盒漏洞掃描器檢測(cè)
web漏洞掃描器檢測(cè)原理和白盒工具不一樣。
首先漏洞掃描器要解決的是識(shí)別出反序列化的請(qǐng)求,在這里需要注意的是web漏洞掃描是無(wú)法通過(guò)爬蟲(chóng)方式直接發(fā)現(xiàn)反序列化接口的,因此往往需要配合其他web漏洞掃描器的組件(例如代理組件)來(lái)識(shí)別反序列化接口,如下圖所示
?
?

?
如今web漏洞掃描器都提供了代理組件來(lái)發(fā)現(xiàn)應(yīng)用的http請(qǐng)求,爬蟲(chóng)組件可通過(guò)前臺(tái)頁(yè)面觸發(fā)請(qǐng)求進(jìn)入代理組件;但在API場(chǎng)景下,還是需要測(cè)試人員進(jìn)行API調(diào)用該操作才能夠產(chǎn)生http請(qǐng)求數(shù)據(jù)。
在截獲到http請(qǐng)求數(shù)據(jù)后,代理組件可以通過(guò)兩種方式判斷一個(gè)請(qǐng)求是否是序列化請(qǐng)求:
1.????? 通過(guò)http請(qǐng)求的Content-Type,具體來(lái)說(shuō)ContentType: application/x-java-serialized-object
是序列化請(qǐng)求的請(qǐng)求頭
2.????? 檢查請(qǐng)求數(shù)據(jù)的開(kāi)頭是否是 0xaced
,有時(shí)候序列化請(qǐng)求不存在正確的content-type,此時(shí)需要根據(jù)數(shù)據(jù)來(lái)判斷是否是序列化請(qǐng)求
在確定一個(gè)接口是序列化接口的時(shí)候會(huì)漏洞掃描器會(huì)發(fā)送探測(cè)payload判斷接口是否有反序列化漏洞,這里的攻擊payload類(lèi)似于1.2節(jié)中使用的ysoserial 工具,由于絕大多數(shù)情況下不可能看到回顯(http返回?cái)?shù)據(jù)沒(méi)有攻擊執(zhí)行結(jié)果),因此只能進(jìn)行盲注,即發(fā)送 sleep 10 這樣的命令,根據(jù)響應(yīng)時(shí)間判斷是否有漏洞。
?