<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Java安全之反射

    一顆小胡椒2022-11-22 10:36:15

    前言

    關于Java安全,反序列化漏洞一直是一個熱門話題,而反序列化漏洞?可以從反射開始說起。通過反射,對象可以通過反射獲取他的類,類可以通過反射拿到所有?法(包括私有),拿到的?法可以調?,總之通過“反射”,我們可以將Java這種靜態語?附加上動態特性。

    入門

    有以下三種方法獲取?個“類”,也就是java.lang.Class對象:

    //1、通過對象調用 getClass() 方法來獲取,通常應用在:比如你傳過來一個 Object類型的對象,而我不知道你具體是什么類,用這種方法
    Person p1 = new Person();
    Class c1 = p1.getClass();
    //2、直接通過類名.class 的方式得到,該方法最為安全可靠,程序性能更高這說明任何一個類都有一個隱含的靜態成員變量 class
    Class c2 = Person.class;
    //3、通過 Class 對象的 forName() 靜態方法來獲取,用的最多,但可能拋ClassNotFoundException異常
    Class c3 = Class.forName("com.ys.reflex.Person");
    

    在安全研究里邊,我們常見的各種payload幾乎都是使用Class.forName方法來獲取類

    forName有兩個函數重載:

    • Class forName(String name)
    • Class forName(String name, boolean initialize, ClassLoader loader)

    第?個就是我們最常?的獲取class的?式,通過類名來獲取,其實可以理解為第?種?式的?個封裝。

    Class.forName(className)
    // 等于
    Class.forName(className, true, currentLoader)
    

    ClassLoader 是什么呢?它就是?個“加載器”,告訴Java虛擬機如何加載這個類。這是另外的一個知識點。Java默認的ClassLoader就是根據類名來加載類,這個類名是類完整路徑,如java.lang.Runtime

    關于第二個參數initialize,我們可以將這個“初始化”理解為類的初始化,我們執行下面這行代碼,JVM會做什么呢?

    Person p = new Person("zhangsan",20);
    
    1. 因為new用到了Person.class.所以會先找到Person.class文件并加載到內存中。 2
    2. 執行該類中的static代碼塊,如果有的話,給Person.class類進行初始化。
    3. 在堆內存中開辟空間,分配內存地址。
    4. 在堆內存中建立對象的特有屬性。并進行默認初始化
    5. 對屬性進行顯示初始化。
    6. 對對象進行構造代碼塊初始化。
    7. 對對象進行對應的構造函數初始化。
    8. 將內存地址付給棧內存中的p變量

    可以分為三類初始化:static代碼塊的初始化,其他初始化,構造函數初始化,其中構造函數初始化會涉及到super類的構造函數初始化,這里不細講了。

    需要注意的是使用class.forName()會對類的靜態代碼塊進行初始化(不會初始化類的構造函數),

    那么我們就可以編寫?個惡意類,將惡意代碼放置在static {}中,從?執?

    import java.lang.Runtime;
    import java.lang.Process;
    public class command {
        static {
            try {
                Runtime rt = Runtime.getRuntime();
                String commands = "calc.exe";
                Process pc = rt.exec(commands);
                pc.waitFor();
            } catch (Exception e) {
            }
        }
    }
    

    進階

    上面可以看到,我們是先import java.lang.Runtime,然后再使用;正常情況下,除了jdk內置類,如果我們想拿到一個類,需要先import才能使用。而使用forName就不需要,這樣我們可以加載任意類進行攻擊。

    獲得類以后,我們可以繼續使用反射來獲取這個類中的屬性、方法,也可以實例化這個類,并調用方法

    在反射類庫中,用于實例化對象的方法有兩個。

    • Class.newInstance():這個方法只需要提供一個class實例就可以實例化對象,如這個方法不支持任何入參,底層是依賴無參數的構造器Constructor進行實例化的。
    • Constructor.newInstance(Object...init args):這個方法需要提供java.lang.reflect.Constructor<T>實例和一個可變參數數組進行對象的實例化,這個方法除了可以傳入構造參數之外,還有一個好處就是可以通過抑制修飾符訪問權限檢查,也就是私有的構造器也可以用于實例化對象。

    我們在構造payload的時候,實例化不成功的原因有以下兩個:

    1. 使用的類沒有無參構造函數,因為newInstance()底層是依賴無參數的構造器實現的,沒有無參構造函數,怎么可能實例化成功
    2. 使用的類構造函數是私有的,我們可以使用Constructor.newInstance(Object...initargs)來實例化

    我們來分析下面這個payload:

    Class cls = Class.forName("java.lang.Runtime");
    Method execMethod = cls.getMethod("exec", String.class);
    Method getRuntimeMethod = cls.getMethod("getRuntime");
    Object runtime = getRuntimeMethod.invoke(cls);
    execMethod.invoke(runtime, "calc.exe");
    

    首先獲取java.lang.Runtime類,接下來獲取這個類的exec方法,接下來又獲取getRuntime方法,下面兩部可能看著有點疑惑了,先來看invoke是干嘛的

    invoke 的作用是執行方法,它的第一個參數是:
    • 如果這個方法是一個普通方法,那么第一個參數是類對象
    • 如果這個方法是一個靜態方法,那么第一個參數是類
    • static修飾的靜態方法會隨著類的定義而被分配和裝載入內存中
    • 普通方法只有在對象創建時,在對象的內存中才有這個方法的代碼段
    這也就是為什么invoke方法參數不同的原因所在。

    常規的方法執行是: Person per = Person.eat(1,2,3)

    反射則是 eat.invoke(Persno,1,2,3)

    有以下代碼段:

    Class cls = Class.forName("java.lang.Runtime");  //獲取類
    Method execMethod =cls.getMethod("exec",String.class); //獲取方法
    execMethod.invoke(cls.newInstance(), "calc.exe");  //實例化類并執行方法
    

    按理說,應該彈計算器啊,為什么報錯了呢?看報錯提示,不能獲取一個被“privite”修飾符修飾的類。

    原來構造方法是私有的,那肯定是實例化不了的,為什么構造方法要搞成私有的,不想讓人用?

    這其中就涉及到一個常見的設計模式--->工廠模式,具體是什么,就不說了。舉例

    我們在做Web開發的時候,數據庫連接只需要建立一次,而不是每次用到數據庫的時候再新建立一個連 接,此時作為開發者你就可以將數據庫連接使用的類的構造函數設置為私有,然后編寫一個靜態方法來 獲取:

    public class DBC {
         private static DBC instance = new DBC();
         public static DBC getInstance() {
           return instance;
        }
         private DBC() {
           // 建立連接的代碼... 
        }
    }
    

    只有只有類初始化的時候會執行一次構造函數,后面只能通過getInstance獲取這個對象,避免建立多個數據庫連接。

    Runtime類就是單例模式,我們只能通過Runtime.getRuntime()來獲取到Runtime對 象。我們將上述Payload進行修改即可正常執行命令了

    public class Main {
        public static void main(String[] args) throws Exception {
            Class cls = Class.forName("java.lang.Runtime");  //獲取類
            Method execMethod =cls.getMethod("exec",String.class); //獲取exec方法
            Method getRuntimeMethod = cls.getMethod("getRuntime");   //獲取getRuntime方法
            Object runtime = getRuntimeMethod.invoke(cls);  //執行getRuntime方法來獲取Runtime類
            execMethod.invoke(runtime, "calc.exe");  //執行方法exec方法
        }
    }
    

    這樣就和一開始的payload對應上了。

    深入

    兩個問題:

    1. 如果一個類沒有無參構造方法,也沒有類似單例模式里的靜態方法,我們怎樣通過反射實例化該類呢?
    2. 如果一個方法或構造方法是私有方法,我們是否能執行它呢?

    第一個問題:我們上節在開始就說過用于實例化對象的方法有兩個,我們只說了第一種,而第二種方法就可以解決這節第一個問題。

    首先我們需要通過反射方法getConstructor()獲得獲得一個Constructor對象,

    和 getMethod 類似, getConstructor 接收的參數是構造函數列表類型,因為構造函數也支持重載, 所以必須用參數列表類型才能唯一確定一個構造函數,聽著有點繞,看以下例子:

    Class cl=Class.forName(Person);
    //獲取到Person(String name,int age) 構造函數
    Constructor con=cl.getConstructor(String.class,int.class);
     //通過構造器對象 newInstance 方法對對象進行初始化,使用有參數構造函數
    Object obj=con.newInstance("神奇的我",12);
    

    我們常用的另一種命令執行的方法ProcessBuilder.start(),我們使用反射來獲取其構造函數,然后調用start()來執行命令:

    public class Main {
        public static void main(String[] args) throws Exception {
            Class cls = Class.forName("java.lang.ProcessBuilder");  //獲取類
            Constructor con = cls.getConstructor(List.class);          // 獲取構造器
            ProcessBuilder process = (ProcessBuilder) con.newInstance(Arrays.asList("calc.exe")); //通過構造器實例化對象
            process.start();
        }
    }
    

    這兒可能有點疑惑了,怎么start就直接彈計算器了?

    ProcessBuilder有兩個構造函數:

    public ProcessBuilder(List<String> command) 
     public ProcessBuilder(String... command)
    

    我們用的是第一個形式的,所以在getConstructor的時候傳入的是List.class

    所以接下來需要用數組的形式傳入calc.exec參數

    這里需要注意,我們通過Constructor實例化對象返回的是一個Object對象,我這里是u強制類型轉換,有時候我們利用漏洞的時候(在表達式上下文中)是沒有這種語法的。所以,我們不能直接執行命令,仍需利用反射來完成這一步,payload如下:

    public class Main {
        public static void main(String[] args) throws Exception {
            Class cls = Class.forName("java.lang.ProcessBuilder");  //獲取類
            Constructor con = cls.getConstructor(List.class);          // 獲取構造器
            Method startMethod = cls.getMethod("start");   //獲取start方法
            Object probulid = con.newInstance(Arrays.asList("calc.exe")); //通過構造器實例化對象
            startMethod.invoke(probulid);
        }
    }
    

    通過getMethod("start")獲取到start方法,然后invoke執行,invoke的參數就是ProcessBuilder Object了。

    如果我們要使用public ProcessBuilder(String... command)這個構造函數,具體的paayload該如何構造呢?

    這又涉及到Java里的可變長參數(avarargs)了。正如其他語言一樣,Java也支持可變長參數,就是當你 定義函數的時候不確定參數數量的時候,可以使用...這樣的語法來表示“這個函數的參數個數是可變的”。 對于可變長參數,Java其實在編譯的時候會編譯成一個數組,也就是說,下面這兩種寫法在底層是等價的:

    public void hello(String[] names) {}
    public void hello(String...names) {}
    

    也由此,如果我們有一個數組,想傳給say函數,只需直接傳即可

    String[] names = {"hello", "world"};
    hello(names);
    

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

    Class clazz = Class.forName("java.lang.ProcessBuilder");
    clazz.getConstructor(String[].class)
    

    在調用 newInstance 的時候,因為這個函數本身接收的是一個可變長參數,我們傳給 ProcessBuilder 的也是一個可變長參數,二者疊加為一個二維數組,所以整個Payload如下:

    public class Main {
        public static void main(String[] args) throws Exception {
            Class cls = Class.forName("java.lang.ProcessBuilder");
            Constructor con = cls.getConstructor(String[].class);
            Method startMethod = cls.getMethod("start");
            Object probuild =con.newInstance(new String[][]{{"calc.exe"}});
            startMethod.invoke(probuild);
        }
    }
    

    那為什么在newInstance時傳入的是一個二維數組呢?

    這是因為在newInstance函數本身接收的是一個可變長參數,我們傳給ProcessBuilder也是一個可變長參數,二者疊加為一個二維數組。這兒比較繞,得轉過彎來

    如果一個方法或構造方法是私有方法,我們是否能執行它呢?

    答案是可以。通過getDeclared系列方法

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

    getDeclaredMethod的具體用法和getMethod類似,getDeclaredConstructor的具體體用法和getConstructor類似

    前面我們說過Runtime這個類的構造函數是私有的,我們需要用Runtime.getRuntime()來 獲取對象。其實現在我們也可以直接用getDeclaredConstructor來獲取這個私有的構造方法來實例化對象,進而執行命令:

    public class Main {
        public static void main(String[] args) throws Exception {
            Class cls = Class.forName("java.lang.Runtime");
            Constructor con = cls.getDeclaredConstructor();
            con.setAccessible(true);
            cls.getMethod("exec", String.class).invoke(con.newInstance(), "calc.exe");
        }
    }
    

    可見,這里使用了一個方法setAccessible,這個是必須的。我們在獲取到一個私有方法后,必須用setAccessible修改它的作用域,否則仍然不能調用。

    初始化構造函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Java安全之反射
    2022-11-22 10:36:15
    前言關于Java安全,反序列化漏洞一直是一個熱門話題,而反序列化漏洞?可以從反射開始說起。通過反射,對象可以通過反射獲取他的類,類可以通過反射拿到所有?附加上動態特性。入門有以下三種方法獲取?個“類”,也就是java.lang.Class對象://1、通過對象調用 getClass() 方法來獲取,通常應用在:比如你傳過來一個 Object類型的對象,而我不知道你具體是什么類,用這種方法
    應用場景wire作為依賴注入的代碼生成工具,非常適合復雜對象的創建。而在大型項目中,擁有一個合適的依賴注入的框架將使得項目的開發與維護十分便捷。中最核心的兩個概念就是Injector和Provider。這些方法接收所需依賴作為參數,創建組件并將其返回Injector?代碼生成命令行在指定目錄下執行?
    使用java和redis實現一個簡單的熱搜功能,具備以下功能: 搜索欄展示當前登陸的個人用戶的搜索歷史記錄,刪除個人歷史記錄 用戶在搜索欄輸入某字符,則將該字符記錄下來 以zset格式存儲的redis中,記錄該字符被搜索的個數以及當前的時間戳 (用了DFA算法,感興趣的自己百度學習吧) 每當用戶查詢了已在redis存在了的字符時,則直接累加個數, 用來獲取平臺上最熱查詢的十條數據。(可以自己寫接
    動態函數PHP中支持一個功能叫 variable function ,變量函數的意思。//最終是system;當一個變量后邊帶括號,那他就被視作一個函數。編譯器會解析出變量的值,然后會去找當前是否存在名為“system()”的函數并執行它。這里就不給實例了,很多免殺案例中都用到了這個特性。也是被瘋狂查殺的特征。回調函數回調函數,簡單來說就是一個函數不是由我直接調用,而是通過另一個函數去調用它。
    Zimbra官方通報了一個RCE漏洞CVE-2022-27925,這是一個 ZIP壓縮包解析導致路徑穿越Getshell。
    Kernel從0開始
    2021-12-10 13:42:20
    網上一大堆教編譯內核的,但很多教程看得特別迷糊。第一次編譯內核時,沒設置好參數,直接把虛擬機編譯炸開了。所以就想著能不能先做個一鍵獲取內核源碼和相關vmlinux以及bzImage的腳本,先試試題,后期再深入探究編譯內核,加入debug符號,所以就有了這個一鍵腳本。
    Activity漏洞挖掘詳解
    2021-10-18 16:22:12
    2Activity漏洞初步介紹1.Activity基本介紹在學習Activity的漏洞挖掘之前,我們先對Activity的基本運行原理有一個初步的認識。
    在2020年夏季,我們發現了一個未知的多模塊C ++工具集,該工具集可用于可追溯到2018年的針對性強的工業間諜攻擊。最初,我們對該惡意軟件感興趣的原因是其稀有性,該活動的明顯針對性以及存在在代碼,基礎架構或TTP...
    VMPWN的入門系列-2
    2023-08-03 09:29:42
    解釋器是一種計算機程序,用于解釋和執行源代碼。與編譯器不同,解釋器不會將源代碼轉換為機器語言,而是直接執行源代碼。即,這個程序接收一定的解釋器語言,然后按照一定的規則對其進行解析,完成相應的功能,從本質上來看依然是一個虛擬機。總的來說,如果輸入字符數小于0x10,string類的大概成員應該如下struct?
    之前看chenx6大佬的博客學習了一下編寫基礎的LLVM Pass,但是那個有很明顯的問題是,作者為了處理Function內部重復引用的多次解密的問題,特判了引用次數,如果存在多處對global string的引用是無法進行混淆的。但是實際的編程中很難不會引用多處字符串,所以那個只能混淆簡單代碼。之后學習了一下pluto-obfuscator項目,里面有一份GlobalEncryption.cpp,借此機會學習一下,順便寫一份New PassManager版本的。runOnModule首先獲取Module的LLVMContext,獲取所有的全局變量,添加到GVs中。
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类