1. 程式人生 > 其它 >Java 反序列化 - commons collection 之困(一)

Java 反序列化 - commons collection 之困(一)

技術標籤:初學者常見問題java反射class

01多餘的碎碎念

說到 java 反序列化,去搜索的話能看到網上有很多分析關於 commons collection 利用鏈的文章,emm 我一開始看不懂,看到很多程式碼的圖頭暈。

這篇文章的話其實是我跟著 p 神的文章一路走下來的,所以整個邏輯會按照 p 神的文章走。那對於反射、動態代理也有一些自己的理解,所以就記錄下來。希望也給你們多一個角度的理解。

02反射

為什麼要先講反射?因為你去看他的利用鏈的實現,會發現都會運用到反射。

java 中有兩個類用來執行命令,一個是 Runtime ,一個是 ProcessBuilder 。那這兩個類都是沒有實現 Serializable (序列化)介面的,也就是不能進行反序列化,我們想想,如果我們執行命令的類不能進行反序列化,也就是不能利用反序列化漏洞還原該類,那我們是不是也就不能執行命令了?這時候我們其實就可以通過反射來建立該類的物件【也就是常說的執行時物件】,呼叫該類的方法。

所以我們首先來介紹一下反射,只要我們知道需要用的類名、方法名及引數型別,我們就能通過反射機制建立任意類物件、呼叫任意類方法。

舉一較常用的例子:

package com.govuln;

import java.lang.reflect.InvocationTargetException;

public class test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = Runtime.class;

clazz.getMethod(“exec”, String.class).invoke(clazz.getMethod(“getRuntime”).invoke(null), “calc.exe”);
}
}
這裡其實就是通過反射實現了呼叫計算器的效果,如果不用反射,我們正常程式碼邏輯就是 Runtime.getRuntime().exec(“calc”); 就好了。

image.png

那麼接下來為了理解上面的程式碼一步步看反射的語法。

我們從下往上推,最開始看他方法呼叫的部分:

正常方法呼叫語法:obj.方法(args)

反射方法呼叫語法:方法.invoke(Object obj, Object… args) ,invoke 中傳入的引數為呼叫此方法的物件,及方法需傳入的引數值。

而此時的方法也應是通過反射得到的。語法:class.getMethod(String name, Class<?>… parameterTypes) ,getMethod 方法中傳入的為我們需呼叫的方法名以及方法的引數型別。

而這裡的 class 就是位元組碼檔案物件,也是類在記憶體中的體現。位元組碼檔案也就是 java 原始檔編譯後生成的 .class 檔案,JVM 將 .class 檔案載入到記憶體執行,將編譯後的 .class 檔案當作位元組碼檔案物件。那麼獲取當前 class 的語法:

1、類.class
2、Class.forName(類的全路徑)
所以我們剛才程式碼中獲取 Runtime 的位元組碼檔案物件其實是通過第一種方式獲取到的。

講到這裡前面程式碼中通過反射執行 Runtime 的 exec 方法應該可以理解得差不多:首先獲取到 Runtime 的位元組碼檔案,接下來獲取到其方法 exec ,然後進行方法呼叫。那這裡還有一個地方就是方法呼叫的時候可能不明白:invoke 傳入的引數,呼叫此方法的物件。clazz.getMethod(“getRuntime”).invoke(null) 為什麼是這樣生成物件的呢?那其實反射獲取到目標物件有兩種方式,第一種就是通過上面程式碼中的形式,這種是通過 Runtime 去呼叫靜態方法 getRuntime ,從而得到一個 Runtime 物件。

image.png

這裡通過這種方式是因為 Runtime 類的構造方法是私有的(意味著非 Runtime 類無法訪問到生成物件時需呼叫的構造方法,作用域的限制)。所以他有一個統一的路徑去獲取到 Runtime 物件,那就是通過呼叫靜態方法 getRuntime 。那麼呼叫靜態方法時,傳入的引數呼叫此方法的物件可以為 null,當然傳入上面程式碼中 clazz 物件也是可以的。

到這裡上面通過反射執行計算器的命令應該就都能理解了。我們還講一下第二種方式來獲取目標物件哦,那就是通過基本的構造方法來獲取到目標物件,在反射中,我們可以通過 clazz.getDeclaredConstructor() 方法獲取到類的私有構造方法,並設定 Accessible 為 true ,即可通過獲取的構造器來 newInstance 獲取到目標物件,newInstance 方法中傳入的引數是建立目標物件時需要的初始化物件,沒有則不傳。所以也就是可以這樣執行命令:

Class clazz = Class.forName(“java.lang.Runtime”);
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Runtime runtime = (Runtime) constructor.newInstance();
clazz.getMethod(“exec”, String.class).invoke(runtime, “calc”);
那麼通過另一個類來執行命令的話,大家可以自己先寫一下:

反射:

Class clazz = ProcessBuilder.class;
clazz.getMethod(“start”).invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList(“calc”)));
正常:

ProcessBuilder processBuilder = new ProcessBuilder(“calc”);
processBuilder.start();

03動態代理

為什麼要講動態代理?emmm 因為利用鏈會用到。 ezgif.gif

動態代理,其實挺像相親的【也到了要相親的年紀,也挺和時宜 233 】。當男孩心悅於某女孩,那麼肯定都會先要經過父母這關的,當父母覺得這個男孩子 ok ,那麼好,你們倆去進行下一步操作。那這裡父母其實就相當於我們的動態代理類,其實也相當於一個攔截過濾器的作用,使其不直接與類物件進行互動而是通過代理物件去進行互動,想要呼叫該類物件的某方法時:

首先獲取到通過代理類生成的代理物件:Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 返回某個物件的代理物件。
第一個引數是 ClassLoader ,指明生成代理物件使用哪個類裝載器;
第二個引數是指明生成哪個物件的代理物件,通過介面指定;
第三個引數是一個實現了 InvocationHandler 介面的物件,該物件有個 invoke 方法裡面實現了代理物件要去做什麼事的邏輯。
接下來通過代理物件呼叫目標物件的方法,實際上先呼叫了 h 的 invoke 方法,接著再去呼叫具體物件的方法。
可以看一個例子理解:

public class App {
public static void main(String[] args) {
InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
proxyMap.put(“hello”, “world”);
String result = (String)proxyMap.get(“hello”);
System.out.println(result);
}
}
public class ExampleInvocationHandler implements InvocationHandler {

protected Map map;

public ExampleInvocationHandler(Map map){
    this.map = map;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.getName().compareTo("get") == 0){
        System.out.println("Hook method:" + method.getName());
        return "hacked object";
    }
    return method.invoke(this.map, args);
}

}
執行結果: image.png

04不重要的結尾

好啦,講到這裡文章已經很長了,下一篇再一起分析 commons collection 的利用鏈吧。點個贊,分享一下吧。

image.png