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