今天在逛先知的時候看到了一篇文章:《探究使用反射進行除Runtime的命令執行方法》https://xz.aliyun.com/t/12446 其中大概講了下命令執行的其他構造方式,但最后沒有給出實體,所以我這里就簡單研究了一下,
概述
在RASP等安全產品防護嚴密的現在,普通的尋找Runtime.getRuntime().exec(cmds)的呼叫已經成為了一件不現實的事情
同樣的,在Java中盛行的反序列化漏洞中,如果將RCE的功能簡單的通過Runtime.getRuntime().exec(cmds)這種結構來進行實作可能大概率也不能達到我們的目的,所以探索一下Runtime的底層實作,使用更加底層且復雜的呼叫來進行RCE功能的實作相對來說更加的可行
命令執行
文章接下里要用到反射技術,這里就不細講了,可以看看其他的文章學習一下:https://www.yuque.com/sanqiushu-dsz56/efe3vx/hchx1p
命令執行 1:Runtime.getRuntime().exec()
// 命令執行 1 Runtime.getRuntime().exec()
Runtime.getRuntime().exec("open -a Calculator");
這個是大家所熟知的 java 中執行外部命令的方法,
首先我們跟蹤Runtime執行命令的程序

exec() 函式在這里接收一個String型別的引數,呼叫 exec() 的另一個多載方法對引數進行處理,將其通過分隔符(默認為空格),將其封裝成了陣列物件

然后繼續呼叫 exec() 多載方法,執行字串陣列型別的命令,

然后通過 ProcessBuilder()....start() 去執行命令了,那么我們自己也是可以通過反射去手動呼叫這個方法的,構造一下,
命令執行 2:ProcessBuilder().start()
// 命令執行 2 ProcessBuilder().start()
String[] cmd_array = new String[] {"open","-a","Calculator"};
Process process = new ProcessBuilder(cmd_array).start();
// 或者
new ProcessBuilder("calc.exe").start(); // 不能有引數
//當然還可以有其他的變體(通過反射):
Class pro = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) pro.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
Class pro = Class.forName("java.lang.ProcessBuilder");
pro.getMethod("start").invoke(pro.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
Class pro = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) pro.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
Class pro = Class.forName("java.lang.ProcessBuilder");
pro.getMethod("start").invoke(pro.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
environment() 是設定環境遍歷的函式,directory() 是設定執行目錄的函式,然而我們都沒傳值,所以直接可以忽略,繼續看 start() 函式

繼續跟進,系統先進行了一番檢查,然后新建了一個 SecurityManager 進行檢測命令是否允許,但默認情況下 SecurityManager 是 null ,不進行檢查,

然后就發現了系統呼叫的是 ProcessImpl.start() 進行命令執行,

那么到這里我們又可以自己構造了
命令執行 3 ProcessImpl.start()
// 命令執行 3 ProcessImpl.start()
String[] cmd_array = new String[] {"open","-a","Calculator"};
Method method_start = Class.forName("java.lang.ProcessImpl").getDeclaredMethod("start",String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
method_start.setAccessible(true);
method_start.invoke(null, cmd_array, null, null, null, false); // 靜態方法不需要傳入實體
繼續跟蹤 ProcessImpl.start() 方法,在經過一番準備之后,系統來到了 UNIXProcess() 構造方法(我是 linux 系統)(Windows 系統是呼叫 create() 方法)

那么是不是意味著我們又可以構造了
命令執行 4 UNIXProcess()
// 命令執行 4 UNIXProcess()
String[] cmd_array = new String[] {"open","-a","Calculator"};
Class<?> clazz = Class.forName("java.lang.UNIXProcess");
Method method_toCString = clazz.getDeclaredMethod("toCString", String.class);
method_toCString.setAccessible(true);
Constructor<?> constructor = clazz.getDeclaredConstructor(byte[].class, byte[].class, int.class, byte[].class,
int.class, byte[].class, int[].class, boolean.class);
constructor.setAccessible(true);
constructor.newInstance((byte[])method_toCString.invoke(null, cmd_array[0]),
new byte[] {45, 97, 0, 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 0} , 2, null, 0, null,
new int[]{-1,-1,-1}, false);

那么繼續往下看 UNIXProcess() 建構式里面的代碼

這里就一句話,呼叫 forkAndExec() 函式,那么我們可以手動呼叫這個函式,開始構造
命令執行 5: forkAndExec()
命令執行 5: forkAndExec()
String[] cmd_array = new String[] {"ls"};
Class<?> clazz = Class.forName("java.lang.UNIXProcess");
Method method_toCString = clazz.getDeclaredMethod("toCString", String.class);
method_toCString.setAccessible(true);
Constructor<?> constructor = clazz.getDeclaredConstructor(byte[].class, byte[].class, int.class, byte[].class,
int.class, byte[].class, int[].class, boolean.class);
constructor.setAccessible(true);
Method method_forkAndExec = clazz.getDeclaredMethod("forkAndExec", int.class, byte[].class, byte[].class,
byte[].class, int.class, byte[].class, int.class, byte[].class, int[].class, boolean.class);
method_forkAndExec.setAccessible(true);
Object o = constructor.newInstance((byte[])method_toCString.invoke(null, cmd_array[0]),
new byte[] {} , 0, null, 0, null, new int[]{-1,-1,-1}, false);
int pid = (int)method_forkAndExec.invoke(o, 2, new byte[]{47, 76, 105, 98, 114, 97, 114, 121, 47, 74,
97, 118, 97, 47, 74, 97, 118, 97, 86, 105, 114, 116, 117, 97, 108, 77, 97, 99, 104, 105, 110,
101, 115, 47, 106, 100, 107, 49, 46, 56, 46, 48, 95, 49, 48, 49, 46, 106, 100, 107, 47, 67,
111, 110, 116, 101, 110, 116, 115, 47, 72, 111, 109, 101, 47, 106, 114, 101, 47, 108, 105,
98, 47, 106, 115, 112, 97, 119, 110, 104, 101, 108, 112, 101, 114, 0},
new byte[]{111, 112, 101, 110, 0},
new byte[]{45, 97, 0, 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 0}, 2, null, 0, null,
new int[]{-1, -1, -1}, false);
不過這個有點牽強,因為需要先構造一個 UNIXProcess 的實體才行,
當然,我這傳入的都是 byte[] ,只能用于驗證,后續需要對照原始碼,將每個引數的生成給自動化才行,
Windows 的最后面的代碼執行途徑不太一樣,但是同理構造即可,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/550653.html
標籤:其他
上一篇:2023年最新微信小程式抓包教程
下一篇:返回列表
