java反序列化漏洞(1)之反射機制


java反射

0x00 java反射簡介

反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性,這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制

將類的各個部分封裝為其他對象
反射是框架設計的靈魂
Java反射不但可以獲取類所有的成員變量名稱,還可以無視權限修飾符實現修改對應的值

Java 反射主要提供以下功能:

  • 在運行時判斷任意一個對象所屬的類;
  • 在運行時構造任意一個類的對象;
  • 在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調用private方法);
  • 在運行時調用任意一個對象的方法

反射的好處:

  1. 可以在程序運行過程中,操作這些對象
  2. 可以解耦,提高程序的可擴展性

0x01 獲取class對象

java反射操作的是java.lang.Class對象,有一下方法獲取一個類的Class對象:

  1. 類名.class
  2. Class.forName("com.demo.classloader.TestClass")
  3. ClassLoader.loadClass("com.demo.classLoader.TestClass")

反射調用內部類的時候需要使用$來代替.,如com.org.test類有一個叫做Hello的內部類,則在調用它的時候要寫成:com.org.test$Hello

0x02 反射java.lang.Runtime

java.lang.Runtime中有一個exec方法可以執行本地命令,在很多payload中都能看見反射Runtime類來執行本地命令

不使用反射執行本地命令代碼片段:

// 輸出命令執行結果
System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(), "UTF-8"));

反射Runtime執行本地命令代碼片段:

這里的IOUtilsorg.apache.commons.io.IOUtils包下的,需要使用maven導入

import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class RunTimeTest {
    public static void main(String[] args) throws IOException {

        test();
    }
    public static void test() throws IOException {
        try {
            // 獲取Runtime類對象
            Class runtimeClass = Class.forName("java.lang.Runtime");

            // 獲取構造方法
            Constructor constructor = runtimeClass.getDeclaredConstructor();
            constructor.setAccessible(true);

            // 創建Runtime類示例
            Object runtimeInstance = constructor.newInstance();

            // 獲取Runtime的exec(String cmd)方法
            Method runtimeMethod = runtimeClass.getMethod("exec",String.class);

            // 調用exec方法,等價於 rt.exec(cmd);
            Process process = (Process) runtimeMethod.invoke(runtimeInstance,"whoami");

            // 獲取命令執行結果
            InputStream in = process.getInputStream();

            // 輸出命令執行結果
            System.out.println(IOUtils.toString(in,"UTF-8"));

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e){
            e.printStackTrace();
        }
    }

    public static void Method2(){
        try {
            // 獲取對象
            Class cls = Class.forName("java.lang.Runtime");
            // 獲取構造方法
            Constructor constructor = cls.getDeclaredConstructor();

            constructor.setAccessible(true);
            // 實例化對象
            Object ob = constructor.newInstance();
            Method mt = cls.getMethod("exec", String.class);

            mt.invoke(ob,"calc");


        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e){
            e.printStackTrace();
        }
    }
}

反射調用Runtime實現本地命令執行的流程如下:

  1. 反射獲取Runtime類對象(Class.forName("java.lang.Runtime"))。
  2. 使用Runtime類的Class對象獲取Runtime類的無參數構造方法(getDeclaredConstructor()),因為Runtime的構造方法是private的我們無法直接調用,所以我們需要通過反射去修改方法的訪問權限(constructor.setAccessible(true))。
  3. 獲取Runtime類的exec(String)方法(runtimeClass1.getMethod("exec", String.class);)。
  4. 調用exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))。

0x03 反射調用類方法

Class 對象提供了一個獲取某個類的所有的成員方法的方法,也可以通過方法名和方法參數類型來獲取指定成員方法

獲取當前類所有的成員方法:

Method[] methods = clazz.getDeclaredMethods();

獲取當前類的指定的成員方法:

Method method = clazz.getDeclaredMethod("方法名");
Method method = clazz.getDeclaredMethod("方法名",類型參數如String.class,多個參數用逗號隔開);

getMethodgetDeclaredMethod都能夠獲取到類成員方法,區別在於getMethod只能獲取到當前類和父類的所有有權限的方法(如:public),而getDeclaredMethod能獲取到當前類的所有成員方法(不包含父類)。

反射調用方法

獲取到java.lang.reflect.Method對象以后我們可以通過Methodinvoke方法來調用類方法

method.invoke第一個參數必須是類實例對象,如果調用的是static方法那么第一個參數值可以傳null,因為在java中調用靜態方法是不需要有類實例的,因為可以直接類名.方法名(參數)的方式調用。
method.invoke的第二個參數不是必須的,如果當前調用的方法沒有參數,那么第二個參數可以不傳,如果有參數那么就必須嚴格的依次傳入對應的參數類型。

0x04 反射獲取Runtime類執行命令

https://xz.aliyun.com/t/4711

部分代碼:

Integer i = 1;
        try {
            Object obj = i.getClass().forName("java.lang.Runtime").
                    getMethod("getRuntime",new Class[]{}).invoke(null);
            System.out.println(obj.getClass().getName());
            i.getClass().forName("java.lang.Runtime").getMethod("exec", String.class).
                    invoke(obj,"calc");
        } catch (Exception e) {
            e.printStackTrace();
        }

getMethod(方法名, 方法類型)
invoke(某個對象實例, 傳入參數)
invoke的作用是執行方法,如果這個參數是一個普通方法,那么第一個參數就是類對象;如果這個方法是一個靜態方法,那么第一個參數是類

0x05 反射小問題

5.1 當一個類沒有無參構造放法,也沒有類似單例模式里的靜態方法時,如何通過反射實例化該類?

在面對以上問題時,需要用到一個新的反射方法getConstructor
這個方法和getMethod相似,getConstructor接受的參數是構造函數列表類型,因為構造函數也支持重載, 所以必須使用參數列表類型才能唯一確定一個構造函數。

在獲取到構造函數后,使用newInstance來執行

ProcessBuilder有兩個構造函數:

  • public ProcessBuilder(List command)
  • public ProcessBuilder(String... command)
    Class clazz = Class.forName("java.lang.ProcessBuilder");    
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(            Arrays.asList("calc.exe")    ));

上面是使用的第一種,所以在傳入的是LIst.class,通過 getMethod("start") 獲取到start方法,然后 invoke 執行, invoke 的第一個參數就是 ProcessBuilder Object了。

對於第二種構造函數,要怎么使用反射來執行呢?

這里就又涉及到java里的可變長參數(varargs)了;當定義函數的時候不確定參數數量時,就可以使用...這樣的語法來表示這個函數的參數個數是可變的 ;對於可變參長數,java在編譯的時候會編譯成一個數組。

那么,對於反射來說,如果要獲取的目標函數里包含可變長參數,只要認為它是數組就行了。
所以,將字符串數組的類String[].class傳給getConstructor,就可以獲取ProcessBuilder的第二種構造參數了

Class clazz = 
Class.forName("java.lang.ProcessBuilder");    
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));

在調用newInstance的時候,因為這個函數本身接受的是一個可變長參數,我們在傳給ProcessBuilder的也是一個可變長參數,二者疊加為一個二維數組

5.2 如果一個方法或構造方法是私有方法,我們是否能執行他

答案是可以,這里可以使用getDeclared系列的反射,

  • getDeclaredMethod系列的方法獲取的是當前類中聲明的方法, 是實在寫在這個類里的,包括私有方法, 但從父類繼承來得就不包含了
  • getMethod系列方法獲取的是當前類中所有公共方法, 包括從父類中繼承的方法

0x06 Java反射機制總結

java反射機制是Java動態性中最為重要的體現,利用反射機制我們可以輕松的實現Java類的動態調用。Java的大部分框架都是采用了反射機制來實現的(如:Spring MVCORM框架等),Java反射在編寫漏洞利用代碼、代碼審計、繞過RASP方法限制等中起到了至關重要的作用

0x07 參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM