有個(gè)人做網(wǎng)站的全國(guó)疫情最新數(shù)據(jù)
前言
還是JAVA安全,哎,真的講不完,太多啦。
今天主要是講一下JAVA中的反射機(jī)制,因?yàn)榉葱蛄谢睦没径际且玫竭@個(gè)反射機(jī)制,還有一些攻擊鏈條的構(gòu)造,也會(huì)用到,所以就講一下。
什么是反射
Java提供了一套反射API,該API由Class類(lèi)與java.lang.reflect類(lèi)庫(kù)組成。
該類(lèi)庫(kù)包含了Field、Method、Constructor等類(lèi)。
對(duì)成員變量,成員方法和構(gòu)造方法的信息進(jìn)行的編程操作可以理解為反射機(jī)制。
從官方定義中就能找到其存在的價(jià)值,在運(yùn)行時(shí)獲得程序或程序集中每一個(gè)類(lèi)型的成員和成員的信息,從而動(dòng)態(tài)的創(chuàng)建、修改、調(diào)用、獲取其屬性,而不需要事先知道運(yùn)行的對(duì)象是誰(shuí)。劃重點(diǎn):在運(yùn)行時(shí)而不是編譯時(shí)。(不改變?cè)写a邏輯,自行運(yùn)行的時(shí)候動(dòng)態(tài)創(chuàng)建和編譯即可)
參考連接:文章 - JAVA安全基礎(chǔ)(二)-- 反射機(jī)制 - 先知社區(qū)
項(xiàng)目創(chuàng)建
創(chuàng)建一個(gè)Java項(xiàng)目,名字叫ReflectDemo。
我這里選擇JAVAEE8。
把這幾個(gè)東西刪除掉,因?yàn)闆](méi)啥用。
新建一個(gè)類(lèi)叫User。
先寫(xiě)入以下代碼,這里說(shuō)明一下以下四個(gè)為成員變量,分別對(duì)應(yīng)3個(gè)不同的屬性,公共屬性、私有屬性、保護(hù)屬性。
public String name = "wlw";public int age = 20;private String gender = "man";protected String job = "sec";
再寫(xiě)入成員方法,一個(gè)為public屬性,一個(gè)為protected屬性。
public void userinfo(String name, int age, String gender, String job) {this.name = name;this.age = age;this.gender = gender;this.job = job;}
protected void users(String name, String gender) {this.name = name;this.gender = gender;System.out.println("user的成員方法"+name);System.out.println("user的成員方法"+gender);}
最后再寫(xiě)入兩個(gè)構(gòu)造方法,可以看到方法名字都是User,和我們類(lèi)的名字一樣。
public User(){}public User(String name){System.out.println("my name"+name);}private User(String name,int age){System.out.println(name);System.out.println(age);}
Class對(duì)象類(lèi)獲取
OK我們的User類(lèi)已經(jīng)寫(xiě)好了,現(xiàn)在來(lái)獲取它里面的方法??赡苡腥擞幸苫笪叶贾肋@個(gè)類(lèi)名叫User為啥還要獲取它呢,是這樣的,你要對(duì)一個(gè)類(lèi)進(jìn)行操作或者調(diào)用,首先必須要獲取這個(gè)類(lèi)的類(lèi)名才行進(jìn)行下一步,并不是說(shuō)我們?nèi)酥李?lèi)名就行了,還得讓代碼知道。
根據(jù)全限定類(lèi)名獲取
我們直接Class.forName(“全路徑類(lèi)名”),運(yùn)行起來(lái)成功獲取類(lèi)名。
public static void main(String[] args) throws ClassNotFoundException {Class aClass = Class.forName("com.sf.maven.reflectdemo.User");System.out.println(aClass);}
根據(jù)類(lèi)名獲取
第二種是直接類(lèi)名.class即可獲取到類(lèi)名
Class bClass = User.class;
System.out.println(bClass);
根據(jù)對(duì)象獲取
第三種是對(duì)象.getClass()直接獲取類(lèi)名。
User user = new User();
Class aClass1 = user.getClass();
System.out.println(aClass1);
通過(guò)類(lèi)加載器獲取
第四種就是我們可以通過(guò)類(lèi)加載器 ClassLoader.getSystemClassLoader().loadClass(“全路徑類(lèi)名”); 來(lái)獲取類(lèi)名。
ClassLoader clsload=ClassLoader.getSystemClassLoader();Class aClass2 = clsload.loadClass("com.sf.maven.reflectdemo.User");System.out.println(aClass2);
利用反射獲取成員變量
前面我們已經(jīng)獲取到類(lèi)名了,現(xiàn)在我們利用反射來(lái)獲取類(lèi)里面的成員變量。
新建一個(gè)類(lèi)叫GetField,在里面寫(xiě)入我們獲取成員變量的方法,先在開(kāi)頭加入我們獲取類(lèi)名的代碼才行。
常見(jiàn)的獲取成員變量的方法有以下幾種。
獲取所有公共成員變量
可以看到輸出的是我們?cè)赨ser定義的兩個(gè)public成員變量,name和age。
Field[] fields = aClass.getFields();
for (Field field : fields) {System.out.println(field);}
獲取所有成員變量
可以看到三個(gè)不同屬性的成員變量均獲取到。
Field[] fields1 = aClass.getDeclaredFields();
for (Field field : fields1) {System.out.println(field);}
獲取單個(gè)公共成員變量
可以看到只獲取了一個(gè)name公共成員變量。
//獲取單個(gè)公共成員變量Field field2 = aClass.getField("name");System.out.println(field2);
獲取任意單個(gè)成員變量
可以看到無(wú)論是什么屬性的成員變量都可以獲取到。
//獲取單個(gè)成員變量Field field3 = aClass.getDeclaredField("gender");System.out.println(field3);
成員變量的值修改和獲取
看完獲取成員變量了,我們?cè)倏匆幌聦?duì)成員變量的值進(jìn)行修改還有獲取。
首先創(chuàng)建一個(gè)對(duì)象也就是獲取類(lèi)名,然后獲取age這個(gè)公共的成員變量,接著獲取user對(duì)象的age值,最后輸出。
可能這里大家有點(diǎn)不明白,前面開(kāi)頭我們不是已經(jīng)獲取類(lèi)名了,為啥這里還要獲取啊。是這樣的,前面獲取的類(lèi)名我們只是為了獲取其里面的age成員變量,此時(shí)我們的age成員變量已經(jīng)賦值給field4了,field4.get(user)就是獲取user類(lèi)里面的age值,換句話說(shuō)后面的是我們要從這個(gè)User類(lèi)里面得到這個(gè)age的值,也就是說(shuō)我們new一個(gè)其它的類(lèi),只要這個(gè)類(lèi)里面有age的成員變量,也會(huì)被獲取!!!(講的有點(diǎn)亂,不對(duì)還請(qǐng)指正)
//獲取成員變量的值User user = new User();Field field4 = aClass.getField("age");Object a = field4.get(user);System.out.println(a);
接著對(duì)User類(lèi)里面age的值進(jìn)行修改,通過(guò) field4.set(user,30); 修改user類(lèi)里面的age值,可以看到輸出為30,但是我們并沒(méi)有去修改user類(lèi)age成員變量的代碼,這就是JAVA反射機(jī)制!!!
field4.set(user,30);
Object b = field4.get(user);
System.out.println(b);
利用反射獲取構(gòu)造方法
獲取類(lèi)中的構(gòu)造方法也是有四種方式。
新建一個(gè)類(lèi)叫GetConstructor,用來(lái)專(zhuān)門(mén)寫(xiě)獲取構(gòu)造方法的代碼,同樣記得再開(kāi)頭加上獲取類(lèi)名的代碼才行。
獲取所有公共構(gòu)造方法
可以看到獲取到了我們前面寫(xiě)好的User(String name) 和 USer() 這兩個(gè)公共的成員方法。
//獲取公共構(gòu)造方法Constructor[] constructors1 = class1.getConstructors();for (Constructor constructor : constructors1) {System.out.println(constructor);}
獲取所有構(gòu)造方法
可以看到無(wú)論是public屬性還是private屬性的構(gòu)造方法都被獲取到了。
//獲取所有構(gòu)造方法Constructor[] constructors2 = class1.getDeclaredConstructors();for (Constructor constructor : constructors2) {System.out.println(constructor);}
獲取單個(gè)公共構(gòu)造方法
這個(gè)和獲取所有的公共構(gòu)造方法的代碼差不多,只不過(guò)是指定了 String 類(lèi)型的構(gòu)造方法,前面我們寫(xiě)好的String類(lèi)型的公共構(gòu)造方法就只有 User(String name) 所以就返回了這個(gè)。
//獲取單個(gè)公共構(gòu)造方法Constructor constructor3 = class1.getConstructor(String.class);System.out.println(constructor3);
獲取單個(gè)私有構(gòu)造方法
和上一個(gè)的差不多,只不過(guò)是多了個(gè)Declared而已。
//獲取單個(gè)私有構(gòu)造方法
Constructor constructor4 = class1.getDeclaredConstructor(String.class, int.class);
System.out.println(constructor4);
對(duì)構(gòu)造方法進(jìn)行操作
setAccessible(true) 臨時(shí)開(kāi)啟對(duì)私有的訪問(wèn),newInstance 使用構(gòu)造方法創(chuàng)建對(duì)象,傳遞參數(shù),允許在運(yùn)行時(shí)通過(guò)?Constructor
?對(duì)象調(diào)用類(lèi)的構(gòu)造方法。
代碼邏輯和上面的成員變量修改差不多,上面是從User類(lèi)里面找age這個(gè)成員變量,這里是從User類(lèi)里面找到符合的構(gòu)造方法??梢钥吹轿覀兊膬蓚€(gè)參數(shù)成功傳入到 User(String name,int age) 這個(gè)構(gòu)造方法里面,并且成功調(diào)用這個(gè)構(gòu)造方法。
//對(duì)構(gòu)造方法進(jìn)行操作
Constructor constructor5 = class1.getDeclaredConstructor(String.class, int.class);
//臨時(shí)開(kāi)啟對(duì)私有構(gòu)造方法的訪問(wèn)
constructor5.setAccessible(true);
User uu = (User) constructor5.newInstance("wlwnb666",30);
System.out.println(uu);
利用反射獲取成員方法
獲取成員方法的方式也是四種,新建一個(gè)類(lèi)叫GetMethod。
獲取包括繼承的所有公共成員方法,可以看到輸出有很多公共成員方法,這是由于它連JAVA中自帶的公共成員方法也一并輸出了,并不單單輸出我們自己寫(xiě)的。
//獲取包括繼承的所有公共成員方法Method[] methods1 = class1.getMethods();for (Method method : methods1) {System.out.println(method);}
獲取不包括繼承的所有成員方法,這里輸出了所有我們自己寫(xiě)的成員方法,并沒(méi)有輸出JAVA內(nèi)置的。
//獲取不包括繼承的所有成員方法Method[] methods2 = class1.getDeclaredMethods();for (Method method : methods2) {System.out.println(method);}
獲取單個(gè)成員方法,這個(gè)要指定我們獲取的成員方法名稱(chēng)為 name ,參數(shù)類(lèi)型也要對(duì)應(yīng)上才行。
//獲取單個(gè)成員方法
Method method3= class1.getDeclaredMethod("users", String.class, String.class);
System.out.println(method3);
和上面幾乎一樣。
//獲取單個(gè)公共成員方法
Method method4= class1.getMethod("userinfo", String.class, int.class, String.class, String.class);
System.out.println(method4);
對(duì)成員方法進(jìn)行調(diào)用,對(duì)我們前面寫(xiě)好的 user 成語(yǔ)方法進(jìn)行調(diào)用。
//調(diào)用成員方法
User user = new User();
Method method5= class1.getDeclaredMethod("users", String.class, String.class);
method5.invoke(user,"wlwnb666","sex");
反序列化鏈條構(gòu)造
OK,反射的知識(shí)點(diǎn)基本都講完了,那現(xiàn)在我們來(lái)構(gòu)造以下利用鏈。
先簡(jiǎn)單寫(xiě)一個(gè)調(diào)用計(jì)算機(jī)的命令執(zhí)行,這個(gè)是調(diào)用JAVA中自帶的包,我們稱(chēng)之為原生調(diào)用。
但是我們想一想,如果是第三方的包,是不是就得要用反射機(jī)制來(lái)得到命令執(zhí)行,由于這里我沒(méi)有引入第三方的包,所以我們就用JAVA自帶的包來(lái)做一下通過(guò)反射實(shí)現(xiàn)命令執(zhí)行的演示,當(dāng)作是外部的包即可。
首先我們可以看到這個(gè)Runtime.getRuntime().exec() 這個(gè)命令執(zhí)行方法是來(lái)自 java,lang.Runtime這個(gè)類(lèi)的。
那么我們就先獲取類(lèi)名和所有的公共成員方法,記得這里要把路徑寫(xiě)全,不能只寫(xiě)Runtime。
可以看到有很多,找到getRuntime這個(gè)方法。
查詢(xún)一下。
現(xiàn)在我們單獨(dú)把這個(gè)getRuntime 獲取出來(lái)。
此外我們還需獲取exec方法,由于exec 需要傳參String類(lèi)型,所以要加String.class。
Method exec = class1.getMethod("exec", String.class);
整個(gè)鏈條如下,由于exec方法屬于實(shí)例方法,所以所以 exec. invoke 的第一個(gè)參數(shù)是 Runtime 實(shí)例。
這里可能有人不理解第三第四行代碼,一開(kāi)始我也不是很懂,后來(lái)查了一下大致理解了。首先我們要知道什么是實(shí)例對(duì)象,實(shí)例指的是通過(guò)某個(gè)類(lèi)(Class)創(chuàng)建出來(lái)的具體對(duì)象,例如:Use user = new Use() 這樣就創(chuàng)建了一個(gè)實(shí)例。那么回到我們的代碼,可以看到?getRuntime 方法返回了 currentRuntime,而currentRuntime 正是開(kāi)頭創(chuàng)建的實(shí)例,也就是說(shuō)getRuntime 方法返回的是一個(gè)實(shí)例。
那么回到我們構(gòu)造的鏈條,Object runtime = method4.invoke(class1); 調(diào)用 getRuntime 方法,并且返回一個(gè)實(shí)例賦值給 runtime ,所以 exec.invoke(runtime, "calc.exe"); 第一個(gè)參數(shù)是runtime,第二個(gè)參數(shù)才是命令。
除了上面的說(shuō)到的鏈條構(gòu)造樣子,還可以這樣子去構(gòu)造,不過(guò)感覺(jué)沒(méi)有第一種簡(jiǎn)單明了。
// 使用 Class.forName 獲取 Runtime 類(lèi)
Class c1 = Class.forName("java.lang.Runtime");// 獲取 Runtime 類(lèi)的默認(rèn)構(gòu)造方法
Constructor m = c1.getDeclaredConstructor();// 設(shè)置構(gòu)造方法為可訪問(wèn)
m.setAccessible(true);// 使用反射調(diào)用 Runtime 類(lèi)的 exec 方法,執(zhí)行系統(tǒng)命令 "calc"
c1.getMethod("exec", String.class).invoke(m.newInstance(), "calc");
不安全的反射對(duì)象
指應(yīng)用程序使用具有反射功能的外部輸入來(lái)選擇要使用的類(lèi)或代碼,
可能被攻擊者利用而輸入或選擇不正確的類(lèi)。繞過(guò)身份驗(yàn)證或訪問(wèn)控制檢查
參考連接:悟空云課堂 | 第七期:不安全的反射漏洞 - 知乎
文章 - JAVA反序列化 - Commons-Collections組件 - 先知社區(qū)