在JAVA中盛行的反序列化漏洞中,如果將RCE的功能簡單的通過Runtime.getRuntime().exec(cmds)這種結構來進行實現可能大概率也不能達到我們的目的,所以探索一下Runtime的底層實現,使用更加底層且復雜的調用來進行RCE功能的實現相對來說更加的可行
概述
在RASP等安全產品防護嚴密的現在,普通的尋找Runtime.getRuntime().exec(cmds)的調用已經成為了一件不現實的事情。
同樣的,在Java中盛行的反序列化漏洞中,如果將RCE的功能簡單的通過Runtime.getRuntime().exec(cmds)這種結構來進行實現可能大概率也不能達到我們的目的,所以探索一下Runtime的底層實現,使用更加底層且復雜的調用來進行RCE功能的實現相對來說更加的可行。
這里主要是對Java中多種命令執行的方式跟蹤源碼進行原理分析、構造利用代碼、集成自研工具。
前置
首先需要對Java中的反射機制有著基本的掌握
通過反射的方式,我們可以獲取到任何類的構造方法,類方法,成員變量,且能夠獲取對應類對象進行對應方法的調用等等目的
- 獲取Class類對象對于類對象的獲取,主要可以通過Class.forName / loadClass的方式來獲取,值得注意的是,在調用Class.forName進行類的加載的時候,將會調用static方法
Class.forName("java.lang.Runtime")
- 1.
- 獲取對應類的構造方法對于獲取類的構造方法,主要可以通過getConstructor或者getDeclaredConstructor這兩種方法來進行實現
兩者的區別主要是前者不能夠反射獲取private修飾的構造方法,而后者能夠獲取。
所以通常使用后者進行構造函數的獲取,傳入的參數就是對應構造方法的參數類。
clazz.getDeclaredConstructor(type.class)
clazz.getConstructor(type.class)
- 1.
- 2.
- 反射獲取成員變量和構造方法類似的,存在有getField和getDeclaredField兩個不同的獲取方法,區別和構造函數類似。
clazz.getField(name)
clazz.getDeclaredField(name)
- 1.
- 2.
- 反射獲取類方法同樣具有getMethod和getDeclaredMethod兩種。
- ...............
一個普通的命令執行是
Runtime.getRuntime().exec("calc");
- 1.
如果使用反射機制可以是
Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(runtime, "calc");
- 1.
或者是其他的一些使用反射機制的變形。
command
跟蹤Runtime
首先我們跟蹤Runtime執行命令的過程。
在這里接收一個String類型的參數,調用exec的另一個重在方法對參數進行處理,將其通過分隔符,將其封裝成了數組對象(這里就是一個字符串)。
之后通過參數是String[]類型的另一個重載方法,通過調用ProcessBuilder類的方法進行執行。
在ProcessBuilder#start方法中,將命令傳遞給了ProcessImpl#start方法進行處理
windows
在windows中主要是在ProcessImpl的構造方法中調用了create方法。
。這個create方法是通過win32的方式創建了一個進程。
linux
在linux下,在ProcessImpl#start的調用中將會創建一個UNIXProcess對象并返回
在UNIXProcess類的構造方法中,調用了forkAndExec這個native方法
創建了一個一個進程,并返回了對應進程的pid
構造命令執行
ProcessBuilder#start
在上面的流程分析中,知道了在Runtime.getRuntime().exec()方法調用的下一層就是使用ProcessBuilder#start方法。
如果hook掉了我們可以通過使用ProcessBuilder類來進行命令執行的構造。
new ProcessBuilder("calc").start();
- 1.
或者使用反射的思路構造
//method_1
Class pro = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) pro.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
//method_2
Class pro = Class.forName("java.lang.ProcessBuilder");
pro.getMethod("start").invoke(pro.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
//method_3
Class pro = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) pro.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
//method_4
Class pro = Class.forName("java.lang.ProcessBuilder");
pro.getMethod("start").invoke(pro.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
ProcessImpl
從上面的分析可以知道,在windows環境下的JDK。
ProcessImpl類的構造方法將會調用create方法執行native方法進行命令執行。
所以我們只需要反射獲取ProcessImpl類的構造方法并實例化就會執行我們的惡意邏輯。
UNIXProcess
上面是針對windows的方式
針對linux,在前面的分析中知道主要是在其start方法中調用了UNIXProcess類的構造方法。
執行forkAndExec這個native方法進行命令執行。
other
甚至于,我們知道最后主要是在create方法(windows)、forkAndExec方法(linux)中執行命令,我們同樣可以通過反射這兩個方法進行命令執行。
本文作者:superLeeH, 轉載請注明來自