範例詳解Java反序列化之反射機制

2022-03-03 19:00:58
本篇文章給大家帶來了關於java的相關知識,其中主要介紹了反射機制的相關問題,動態獲取程式資訊以及動態呼叫物件的功能稱為Java語言的反射機制,希望對大家有幫助。

推薦學習:《》

每次聽到大佬在講或者看論壇等一些方式學java反序列化漏洞時,都會有一個詞叫做反射機制,大佬順勢藉著這個詞,就給你造出一個payload,對於剛學java反序列化的我們,可能有點會懵圈,反正我是懵了,所以就趕緊學了一波,不然和大佬差距越來越大。所以這篇文章主要講述java反射機制

Java反射機制

Java的反射(reflection)機制是指在程式的執行狀態中,可以構造任意一個類的物件,可以瞭解任意一個物件所屬的類,可以瞭解任意一個類的成員變數和方法,可以呼叫任意一個物件的屬性和方法。這種動態獲取程式資訊以及動態呼叫物件的功能稱為Java語言的反射機制。反射被視為動態語言的關鍵。

我不太擅長文字表達,還是上圖操作把

不用反射機制的例子

//定義一個animals介面interface animals {
    public abstract void print();}//定義類來實現animals介面的抽象方法class Dog implements animals {
    public void print() {
        System.out.println("Dog");
    }}class Cat implements animals {
    public void print() {
        System.out.println("Cat");
    }}// 構造一個zoo類// 之後如果我們在新增其他的範例的時候只需要修改zoo類class zoo {

    public static animals getInstance(String animalsName) {
        animals a = null;
        if ("Dog".equals(animalsName)) {
            a = new Dog();
        }
        if ("Cat".equals(animalsName)) {
            a = new Cat();
        }
        return a;
    }}public class reflection {
    public static void main(String[] args) {
        //藉助zoo類尋找對應的類來實現介面
        animals a=zoo.getInstance("Cat");
        if(a!=null)
            a.print();
    }}

這時候新增動物,只需要

  • 新增類
  • 修改zoo
  • 修改main函數的動物類

把上面修改為反射機制

//定義一個animals介面interface animals {
    public abstract void print();}//定義類來實現animals介面的抽象方法class Dog implements animals {
    public void print() {
        System.out.println("Dog");
    }}class Cat implements animals {
    public void print() {
        System.out.println("Cat");
    }}// 構造一個zoo類// 之後如果我們在新增其他的範例的時候只需要修改zoo類class zoo {

    public static animals getInstance(String className) {
        animals a = null;
        try {
            //藉助Class.forName尋找類名,並用newInstance範例化類似於new
            a = (animals) Class.forName(className).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return a;
    }}public class reflection {
    public static void main(String[] args) {
        //藉助zoo類尋找對應的類來實現介面(classname為當前包名加類名)
        animals a = zoo.getInstance("com.cc1.Dog");
        if (a != null)
            a.print();
    }}

這時候新增動物只需要

  • 新增類
  • 修改main函數的動物類

省了一步,傳入類名可控,發現好像是存在的類都可以調

反射機制方法

我們用的最多的可能是

  • forName(呼叫類)
  • getMethod(呼叫類下方法)
  • invoke(執行)
  • newInstance(範例化物件)
Class.forName(className).getMethod(methodName).invoke(Class.forName(className).newInstance());

下面我們用反射機制來彈出計算機(calc)或者記事本(notepad)

由於彈出計算機有點多這次我就彈記事本把,總而言之,能彈出來就很美妙

Runtime.getRuntime().exec("notepad");

在這裡插入圖片描述
我們看下getRuntime函數
在這裡插入圖片描述
得知,該函數是Runtime類獲取物件的方式,個人感覺是每用一次就調一次比較麻煩,為了不呼叫一次建立一個物件所以封裝成了函數

類物件獲取方式

  • Class.forName(類名獲取)
  • zoo.class(已經載入過的類)
  • obj.class(範例)
    在這裡插入圖片描述

類初始化

修改zoo類,增加初始塊、靜態初始塊、和建構函式

class zoo {
    //初始塊
    {
        System.out.println("1  " + this.getClass());
    }

    //靜態初始塊
    static {
        System.out.println("2  " + zoo.class);
    }

    public zoo() {
        System.out.println("3  " + this.getClass());
    }

    public static animals getInstance(String className) {
        animals a = null;
        try {
            //藉助Class.forName尋找類名,並用newInstance範例化類似於new
            a = (animals) Class.forName(className).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return a;
    }}

類初始化執行順序:靜態初始塊
在這裡插入圖片描述
類範例化執行順序:靜態初始塊 - > 初始塊 - > 建構函式
在這裡插入圖片描述
由此得知,類初始化和類範例化不一樣

接下來增加zoo1類繼承zoo類

class zoo1 extends zoo{
    //初始塊
    {
        System.out.println("11  " + this.getClass());
    }

    //靜態初始塊
    static {
        System.out.println("12  " + zoo.class);
    }

    public zoo1() {
        System.out.println("13  " + this.getClass());
    }}

子類初始化順序:父類別靜態初始化塊 - > 子類靜態初始化塊
在這裡插入圖片描述
子類範例化順序:父類別靜態初始化塊 - > 子類靜態初始化塊 - > 父類別初始化塊 - > 父類別建構函式 - > 子類初始化塊 - >子類建構函式
在這裡插入圖片描述
以上可以得知,當使用Class.forName時,且類靜態初始化塊可控,可以執行任意程式碼

呼叫內部類

Class.forName(「java.lang.Runtime」)來獲取類(java.lang.Runtime是Runtime類的完整路徑)

getMethod

getMethod 的作用是通過反射獲取類的某個特定的公有方法。
java支援類過載,但不能僅通過一個函數名確定一個函數,所以在呼叫getMethod時,需要傳給它方法的引數型別列表
Class.forName(「java.lang.Runtime」).getMethod(「exec」, String.class)

invoke

靜態和動態方法的區別
在這裡插入圖片描述

invoke方法在getMethod類下,作用時傳遞引數,執行方法
public Object invoke(Object obj, Object… args)
第一個引數是getMethod獲取的方法的類物件(如果方法是靜態方法則傳類)
獲取exec函數的類物件
Class.forName(「java.lang.Runtime」).getMethod(「getRuntime」).invoke(Class.forName(「java.lang.Runtime」))
由於getRuntime是靜態方法,所以傳類
invoke(Class.forName(「java.lang.Runtime」).getMethod(「getRuntime」).invoke(Class.forName(「java.lang.Runtime」)),「calc.exe」)

最後我們合併一下

Class.forName("java.lang.Runtime").
                getMethod("exec", String.class).
                invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")), "notepad");

在這裡插入圖片描述

指定構造方法生成範例

String str="notepad";ProcessBuilder pb = new ProcessBuilder(str);pb.start();

getConsturctor(函數可以選定指定介面格式的建構函式(由於建構函式也可以根據引數來進行過載)
選定後我們可以通過newInstance(),並傳入建構函式的引數執行建構函式

ProcessBuilder類有兩個建構函式

  • public ProcessBuilder(String… command)(String…變長的字串陣列String[].class)
  • public ProcessBuilder(List command)

分別使用構造方法

  • Class.forName(「java.lang.ProcessBuilder」).getConstructor(String[].class).newInstance(new String[][]{{「notepad」}})
  • Class.forName(「java.lang.ProcessBuilder」).getConstructor(List.class).newInstance(Arrays.asList(「notepad」))

執行完構造方法範例後,在進行強制轉化使用start函數即可

( (ProcessBuilder) Class.forName(「java.lang.ProcessBuilder」).getConstructor(List.class).newInstance(Arrays.asList(「notepad」))).start();

實際中,肯定用不了,哪有這麼好的事,還是接著反射把

Class.forName(「java.lang.ProcessBuilder」).getMethod(「start」).invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList(「notepad」)));
在這裡插入圖片描述
這裡可能有人會好奇我寫的裡那的另一個建構函式,String…command這個傳參為什麼用new String[][]{{「notepad」}},不應該是new String[]{「notepad」},現在用應該的

((ProcessBuilder) Class.forName(「java.lang.ProcessBuilder」).getConstructor(String[].class).newInstance(new String[]{「notepad」})).start();

在這行打斷點偵錯
在這裡插入圖片描述
我們傳的是一個字串陣列到了範例化的時候變成了一個字串,再看看另一個建構函式(List)

( (ProcessBuilder) Class.forName(「java.lang.ProcessBuilder」).getConstructor(List.class).newInstance(Arrays.asList(「notepad」))).start();

依舊還是這行打斷點

在這裡插入圖片描述
由此可知,List傳入時會被當作Object的第一項,而String[]會被當做Object,所以多加一層[]{}

執行私有方法

通過函數getDeclaredConstructor獲取私有方法,再利用setAccessible(true)打破私有方法限制

Class cls = Class.forName("java.lang.Runtime"); 
Constructor m = cls.getDeclaredConstructor();
 m.setAccessible(true); 
 cls.getMethod("exec", String.class).invoke(m.newInstance(), "notepad");

推薦學習:《》

以上就是範例詳解Java反序列化之反射機制的詳細內容,更多請關注TW511.COM其它相關文章!