Java審計之命令執行篇
0x00 前言
在Java中能執行命令的類其實並不多,不像php那樣各種的命令執行函式。在Java中目前所知的能執行命令的類也就兩種,分別是Runtime和 ProcessBuilder類。
0x01 Runtime 執行命令分析
關於Runtime具體的使用可以看這篇文章,反射去呼叫Runtime。
@WebServlet("/execServlet")
public class execServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String exec = request.getParameter("exec");
Process res = Runtime.getRuntime().exec(exec);
InputStream inputStream = res.getInputStream();
ServletOutputStream outputStream = response.getOutputStream();
int len;
byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
}
這裡來執行一下,傳入一個命令看看能不能正常執行。
http://localhost:8080/untitled9_war_exploded/execServlet?exec=ipconfig
我們來看看他具體的實現,首先跟蹤一下getRuntime()方法。
看到呼叫方法就不做任何的處理就直接返回了currentRuntime物件,上面可以看到currentRuntime是Runtime的例項物件。也就是說每次呼叫getRuntime()方法都會new一個Runtime的物件。
官方文件說明:
每個Java應用程式都有一個Runtime類的Runtime ,允許應用程式與執行應用程式的環境進行介面。 當前執行時可以從getRuntime方法獲得。
應用程式無法建立自己的此類的例項。
再來跟蹤一下exec方法,看看exec方法的具體實現
exec中有多個過載方法,在跟蹤返回值的exec
還是他的過載方法,再跟蹤
這裡會發現不一樣了,返回了
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
在跟蹤的時候會發現,我們傳入的ipconfig會在cmdarray變數裡面進行傳入,其他值都是null。也就是說該類的底層其實還是ProcessBuilder這個類來進行實現的。
前面的Process res = Runtime.getRuntime().exec(exec);
返回型別為 Process,是通過new ProcessBuilder並傳入命令,再去呼叫start方法返回得來的。
但我們後面的res.getInputStream();
,getInputStream();是怎麼來的呢?Process 是一個抽象類,包含了6個抽象方法。
abstract public OutputStream getOutputStream();
abstract public InputStream getInputStream();
abstract public InputStream getErrorStream();
abstract public int waitFor() throws InterruptedException;
abstract public int exitValue();
abstract public void destroy();
跟蹤一下,這幾個方法,呼叫的方法,除了start方法外,其他的幾個方法都是進行設定值。這裡就不一一演示了。
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
跟蹤start方法
可以看到start方法裡面,呼叫了process的實現類。
0x02 ProcessBuilder中命令執行
package com.test;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
@WebServlet("/exec2Servlet")
public class exec2Servlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String exec = request.getParameter("exec");
ServletOutputStream outputStream = response.getOutputStream();
ProcessBuilder processBuilder = new ProcessBuilder(exec);
Process res = processBuilder.start();
InputStream inputStream = res.getInputStream();
int len ;
byte[] bytes =new byte[1024];
while ((len = inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
}
}
}
使用ProcessBuilder的方式也是可以執行命令的。
0x03 內容補充
後面專門開一個標題,做一個前面知識的補充。
Process類
Process類方法:
destroy()
殺掉子程式。
exitValue()
返回子程式的出口值。
InputStream getErrorStream()
獲得子程式的錯誤流。
InputStream getInputStream()
獲得子程式的輸入流。
OutputStream getOutputStream()
獲得子程式的輸出流。
waitFor()
導致當前執行緒等待,如果必要,一直要等到由該 Process 物件表示的程式已經終止。
前面會發現的一個問題在這裡做一個解疑,process既然是抽象類,不能new物件,獲取到他的例項類的呢?
前面我們除錯程式碼的時候,發現了可以使用ProcessBuilder的Start方法進行返回,第二種方法就是Runtime的exec方法,但Runtime的exec方法底層依然是ProcessBuilder的Start方法進行實現的。
這裡還有必要說的一個問題,ProcessBuilder與Runtime.exec()的區別是什麼?
在網上查閱了一些思路得出了以下的結論。
ProcessBuilder.start() 和 Runtime.exec() 方法都被用來建立一個作業系統程式(執行命令列操作),並返回 Process 子類的一個例項,該例項可用來控制程式狀態並獲得相關資訊。
ProcessBuilder.start() 和 Runtime.exec()傳遞的引數有所不同,Runtime.exec()可接受一個單獨的字串,這個字串是通過空格來分隔可執行命令程式和引數的;也可以接受字串陣列引數。而ProcessBuilder的建構函式是一個字串列表或者陣列。列表中第一個引數是可執行命令程式,其他的是命令列執行是需要的引數。
參考文章
https://honeypps.com/java/process-builder-quick-start/
0x04 結尾
在遇到一些問題的時候,多打debug也是個不錯的選擇,多打debug能比較清晰的分析到問題所在。例如除錯程式碼的時候,可以打個debug一層層去分析也會發現一些有意思的東西。