更新時(shí)間:2021-11-04 10:08:12 來源:動力節(jié)點(diǎn) 瀏覽1659次
此工具不用于查找漏洞。相反,它使用已知的 source->…->sink 技巧或其類似的特性來發(fā)現(xiàn)分支利用鏈或新的利用鏈。
這個(gè)工具在整個(gè)應(yīng)用程序的類路徑中尋找一個(gè)鏈。
該工具執(zhí)行一些合理的風(fēng)險(xiǎn)估計(jì)(污點(diǎn)判斷、污點(diǎn)轉(zhuǎn)移等)。
這個(gè)工具會產(chǎn)生誤報(bào)而不是漏報(bào)(其實(shí)還是會漏掉的,這是由作者使用的策略決定的,可以在下面的分析中看到)。
該工具基于字節(jié)碼分析。對于Java應(yīng)用,很多時(shí)候我們沒有源代碼,只有War包、Jar包或者class文件。
此工具不會生成可直接使用的 Payload。具體的利用結(jié)構(gòu)也需要人工參與。
在Java學(xué)習(xí)中,序列化是將對象的狀態(tài)信息轉(zhuǎn)換為可以存儲或傳輸?shù)男问降倪^程。轉(zhuǎn)換后的信息可以存儲在磁盤上。在網(wǎng)絡(luò)傳輸過程中,可以是字節(jié)、XML、JSON等形式,將字節(jié)、XML、JSON等形式的信息還原為對象的逆過程稱為反序列化。
在Java中,對象序列化和反序列化廣泛應(yīng)用于RMI(遠(yuǎn)程方法調(diào)用)和網(wǎng)絡(luò)傳輸。
JDK(對象輸入流)
XStream(XML,JSON)
杰克遜(XML,JSON)
根森(JSON)
JSON-IO(JSON)
FlexSON(JSON)
Fastjson(JSON)
…
不同的反序列化庫在反序列化不同的類時(shí)有不同的行為。不同的“魔法方法”會被自動調(diào)用,這些自動調(diào)用的方法可以作為反序列化的入口點(diǎn)(源碼)。如果這些自動調(diào)用的方法調(diào)用了其他的子方法,那么調(diào)用鏈中的一個(gè)子方法也可以作為源,相當(dāng)于知道了調(diào)用鏈的前端,從一個(gè)子方法開始尋找不同的分支。一些危險(xiǎn)的方法(sink)可以通過方法的層調(diào)用來達(dá)到。
對象輸入流
比如一個(gè)類實(shí)現(xiàn)了Serializable接口,那么ObjectInputStream.readobject在反序列化的時(shí)候會自動找到該類的readObject、readResolve等方法。
比如一個(gè)類實(shí)現(xiàn)了Externalizable接口,那么ObjectInputStream.readobject在反序列化的時(shí)候會自動找到這個(gè)類的readExternal等方法。
杰克遜
ObjectMapper.readValue反序列化一個(gè)類時(shí),會自動查找反序列化類的無參構(gòu)造函數(shù)、包含基類型參數(shù)的構(gòu)造函數(shù)、屬性的setter、屬性的getter等。
…
在接下來的分析中,以JDK自帶的ObjectInputStream為例。
控制數(shù)據(jù)類型 => 控制代碼
在反序列化漏洞中,如果我們控制了數(shù)據(jù)類型,我們就控制了代碼。這是什么意思?根據(jù)理解,寫了下面的例子:
public class TestDeserialization {
interface Animal {
public void eat();
}
public static class Cat implements Animal,Serializable {
@Override
public void eat() {
System.out.println("cat eat fish");
}
}
public static class Dog implements Animal,Serializable {
@Override
public void eat() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("dog eat bone");
}
}
public static class Person implements Serializable {
private Animal pet;
public Person(Animal pet){
this.pet = pet;
}
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
pet = (Animal) stream.readObject();
pet.eat();
}
}
public static void GeneratePayload(Object instance, String file)
throws Exception {
//Serialize the constructed payload and write it to the file
File f = new File(file);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//Read the written payload and deserialize it
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
Object obj = in.readObject();
System.out.println(obj);
in.close();
}
public static void main(String[] args) throws Exception {
Animal animal = new Dog();
Person person = new Person(animal);
GeneratePayload(person,"test.ser");
payloadTest("test.ser");
// Animal animal = new Cat();
// Person person = new Person(animal);
// GeneratePayload(person,"test.ser");
// payloadTest("test.ser");
}
}
為方便起見,將所有類都寫在一個(gè)類中進(jìn)行測試。在Person類中有Animal類的屬性pet,它是Cat和Dog的接口。在序列化中,我們可以控制Per的pet是Cat對象還是Dog對象,所以在反序列化中,pet.eat()inreadObject的具體方向是不同的。如果pet是Cat類對象,就不會去執(zhí)行有害代碼Runtime.getRuntime().exec("calc");,但是如果pet是Dog類對象,它就會去執(zhí)行有害代碼。
即使有時(shí)在聲明時(shí)為類屬性分配了特定對象,它仍然可以通過 Java 中的反射進(jìn)行修改。如下:
public class TestDeserialization {
interface Animal {
public void eat();
}
public static class Cat implements Animal, Serializable {
@Override
public void eat() {
System.out.println("cat eat fish");
}
}
public static class Dog implements Animal, Serializable {
@Override
public void eat() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("dog eat bone");
}
}
public static class Person implements Serializable {
private Animal pet = new Cat();
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
pet = (Animal) stream.readObject();
pet.eat();
}
}
public static void GeneratePayload(Object instance, String file)
throws Exception {
//Serialize the constructed payload and write it to the file
File f = new File(file);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//Read the written payload and deserialize it
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
Object obj = in.readObject();
System.out.println(obj);
in.close();
}
public static void main(String[] args) throws Exception {
Animal animal = new Dog();
Person person = new Person();
//Modify private properties by reflection
Field field = person.getClass().getDeclaredField("pet");
field.setAccessible(true);
field.set(person, animal);
GeneratePayload(person, "test.ser");
payloadTest("test.ser");
}
}
在Person 類中,您不能通過構(gòu)造函數(shù)或setter 方法或其他方法為pet 賦值。該屬性在聲明時(shí)就已經(jīng)定義為Cat類的對象,但是使用反射可以將pet修改為Dog類的對象,所以反序列化的時(shí)候還是會跑到有害代碼中去。
這只是我個(gè)人對作者的看法:“控制數(shù)據(jù)類型,你控制代碼”。在Java反序列化漏洞中,很多時(shí)候是利用Java的多態(tài)特性來控制代碼的方向,最終達(dá)到作惡的目的。
在上面的例子中,我們可以看到Person的readobject方法在反序列化的時(shí)候并沒有被手動調(diào)用。當(dāng) ObjectInputStream 反序列化對象時(shí)會自動調(diào)用它。作者將自動調(diào)用的方法調(diào)用為“魔術(shù)方法”。
使用 ObjectInputStream 反序列化時(shí)的幾種常見魔術(shù)方法:
Object.readObject()
Object.readResolve()
Object.finalize()
…
一些可序列化的 JDK 類實(shí)現(xiàn)了上述方法,并且還會自動調(diào)用其他方法(可以用作已知的入口點(diǎn)):
哈希表
Object.hashCode()
Object.equals()
優(yōu)先隊(duì)列
Comparator.compare()
Comparable.CompareTo()
…
一些水槽:
Runtime.exec(),在目標(biāo)環(huán)境中直接執(zhí)行命令的最簡單直接的方式
Method.invoke(),需要正確選擇方法和參數(shù),并通過反射執(zhí)行Java方法
RMI/JNDI/JRMP等,通過引用遠(yuǎn)程對象間接實(shí)現(xiàn)任意代碼執(zhí)行的效果
…
以上就是關(guān)于“Java反序列化工具:Gadgetinspector First Glimpse”的介紹,大家如果想了解更多相關(guān)知識,不妨來關(guān)注一下動力節(jié)點(diǎn)的Java開發(fā)工具,里面對于工具的介紹有很多,大家可以多去學(xué)習(xí)一下。
初級 202925
初級 203221
初級 202629
初級 203743