Java審計之命令執行篇

nice_0e3發表於2020-09-21

Java審計之命令執行篇

0x00 前言

在Java中能執行命令的類其實並不多,不像php那樣各種的命令執行函式。在Java中目前所知的能執行命令的類也就兩種,分別是Runtime和 ProcessBuilder類。

0x01 Runtime 執行命令分析

關於Runtime具體的使用可以看這篇文章,反射去呼叫Runtime。

Java學習之反射篇

@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一層層去分析也會發現一些有意思的東西。

相關文章