Java中如何执行系统命令


=Start=

缘由:

在学习Java的过程中不断用文章进行整理总结(常用功能的Java实现),争取早日能较为熟练的使用Java进行开发。

正文:

参考解答:

在Java中执行系统命令可以使用 ProcessBuilder 或者 Runtime.exec 方法。

package com.ixyzero.learn.misc;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Created by ixyzero on 2018/6/12.
 */
public class TestExecSystemCommand {
    public static void main(String[] args) {
        try {
            String line = null;
            String[] cmd = { "/bin/sh", "-c", "ps aux | grep mysql" }; //要执行的命令
            Process p = Runtime.getRuntime().exec(cmd);
            BufferedReader in =
                    new BufferedReader(new InputStreamReader(p.getInputStream()));
            while ((line = in.readLine()) != null) {
                System.out.println(line);
            }
            in.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }
}

上面这种常规的简单方法可能会hang住JVM。发生这种情况是因为inputStream缓冲区被塞满导致阻塞,直到它被读取;将 waitFor() 方法放在读取 inputStream 之后可以解决这个问题,但前提是errorStream没有被填满。

所以,正确的做法就是——新建2个线程去处理inputStream和errorStream缓冲区,然后再调用 waitFor() 方法。

package com.ixyzero.learn.misc;

import java.io.IOException;
import java.io.InputStream;

/**
 * Created by ixyzero on 2018/6/12.
 */
public class TestExecuteCmd {
    public static void main(String[] args) {
        executeCmd("ls -oa"); // 不支持管道操作符,因为是直接以string的形式传给exec方法的
        System.out.println();

        String[] cmd = { "/bin/sh", "-c", "ps aux | grep mysql" };
        executeCmdArray(cmd);
    }

    private static void executeCmd(String string) {
        try {
            Process aProcess = Runtime.getRuntime().exec(string);

            // These two thread shall stop by themself when the process end
            Thread pipeThread = new Thread(new StreamReader(aProcess.getInputStream()));
            Thread errorThread = new Thread(new StreamReader(aProcess.getErrorStream()));

            pipeThread.start();
            errorThread.start();

            aProcess.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
    }

    private static void executeCmdArray(String[] cmd) {
        try {
            Process aProcess = Runtime.getRuntime().exec(cmd);

            // These two thread shall stop by themself when the process end
            Thread pipeThread = new Thread(new StreamReader(aProcess.getInputStream()));
            Thread errorThread = new Thread(new StreamReader(aProcess.getErrorStream()));

            pipeThread.start();
            errorThread.start();

            aProcess.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
    }

}

//Replace the following thread with your intends reader
class StreamReader implements Runnable {

    private InputStream Pipe;

    public StreamReader(InputStream pipe) {
        if(pipe == null) {
            throw new NullPointerException("bad pipe");
        }
        Pipe = pipe;
    }

    public void run() {
        try {
            byte buffer[] = new byte[2048];

            int read = Pipe.read(buffer);
            while(read >= 0) {
                System.out.write(buffer, 0, read);

                read = Pipe.read(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(Pipe != null) {
                try {
                    Pipe.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

如果希望更灵活的获取执行命令的返回值、输出、错误输出等信息,可以参考一下「Java exec – execute system processes with Java ProcessBuilder and Process」里面的基于 ProcessBuilder 和 新建线程处理inputStream和errorStream缓冲区 的方式实现的类。

参考链接:
=END=

《 “Java中如何执行系统命令” 》 有 5 条评论

  1. 利用Java的Stream API处理海量数据
    https://blog.dteam.top/posts/2019-03/%E5%88%A9%E7%94%A8java%E7%9A%84stream-api%E5%A4%84%E7%90%86%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE.html
    `
    ResultScanner是一个Iterable对象。那我们可以这么考虑,能不能不要在迭代中一直拼接Buffer,而是迭代中每格式化一个Buffer之后,就把Buffer写入响应流中呢?这样不就解决了拼接buffer造成内存溢出的问题了吗?而且可以让响应时间进一步缩短。

    这里就是Java 8新引入的java.util.Stream API了,不同于nio的Stream,这里的Stream是为了实现流式处理的接口,而且可以和java 8新加入的Lambda表达式进行更加灵活的处理。Stream就像水流一样,处理完就丢掉了,不会向前遍历,可以很方便的构造数据处理管道,正好符合我们的这个场景。

    而Iterable对象是非常容易转成Stream的,在java 8就提供了一个工具类java.util.stream.StreamSupport,可以很方便的把Iterable对象转成Stream。
    `

  2. Java本地命令执行
    https://appts4jvi.zhishibox.net/b/5d644b6f81cbc9e40460fe7eea3c7925
    `
    Runtime.exec(xxx)调用链如下:

    java.lang.UNIXProcess.(UNIXProcess.java:247)
    java.lang.ProcessImpl.start(ProcessImpl.java:134)
    java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    java.lang.Runtime.exec(Runtime.java:620)
    java.lang.Runtime.exec(Runtime.java:450)
    java.lang.Runtime.exec(Runtime.java:347)
    org.apache.jsp.runtime_002dexec2_jsp._jspService(runtime_002dexec2_jsp.java:118)

    通过观察整个调用链我们可以清楚的看到exec方法并不是命令执行的最终点,执行逻辑大致是:

    1. Runtime.exec(xxx)
    2. java.lang.ProcessBuilder.start()
    3. new java.lang.UNIXProcess(xxx)
    4. UNIXProcess 构造方法中调用了 forkAndExec(xxx) native方法。
    5. forkAndExec 调用操作系统级别 fork->exec(*nix)/CreateProcess(Windows)执行命令并返回fork/CreateProcess的PID。

    有了以上的调用链分析我们就可以深刻的理解到Java本地命令执行的深入逻辑了,切记Runtime和ProcessBuilder并不是程序的最终执行点!
    `

  3. Java本地命令执行
    https://appts4jvi.zhishibox.net/b/5d644b6f81cbc9e40460fe7eea3c7925
    `
    Java本地命令执行:
    Runtime.getRuntime().exec(“cmd_here”) 命令执行
    ProcessBuilder(“cmd_here”) 命令执行
    UNIXProcess/ProcessImpl
    反射UNIXProcess/ProcessImpl执行本地命令
    forkAndExec命令执行-Unsafe+反射+Native方法调用
    JNI命令执行

    # Java本地命令执行总结
    很多人对Java本地命令执行的理解不够深入导致了他们无法定位到最终的命令执行点,去年给OpenRASP提过这个问题,他们只防御到了 ProcessBuilder.start() 方法,而我们只需要直接调用最终执行的 UNIXProcess/ProcessImpl 实现命令执行或者直接反射UNIXProcess/ProcessImpl的forkAndExec方法就可以绕过RASP实现命令执行了。

    如果RASP把 UNIXProcess/ProcessImpl 类的构造方法给拦截了我们是不是就无法执行本地命令了?其实我们可以利用Java的几个特性就可以绕过RASP执行本地命令了,具体步骤如下:

    1. 使用sun.misc.Unsafe.allocateInstance(Class)特性可以无需new或者newInstance创建UNIXProcess/ProcessImpl类对象。
    2. 反射UNIXProcess/ProcessImpl类的forkAndExec方法。
    3. 构造forkAndExec需要的参数并调用。
    4. 反射UNIXProcess/ProcessImpl类的initStreams方法初始化输入输出结果流对象。
    5. 反射UNIXProcess/ProcessImpl类的getInputStream方法获取本地命令执行结果(如果要输出流、异常流反射对应方法即可)。

    Java可以通过JNI的方式调用动态链接库,我们只需要在动态链接库中写一个本地命令执行的方法就行了。

    Java本地命令执行是一个非常高危的漏洞,一旦被攻击者利用后果不堪设想。这个漏洞原理一样是非常简单且容易被发现。开发阶段我们应该尽可能的避免调用本地命令接口,如果不得不调用那么请仔细检查命令执行参数,严格检查(防止命令注入)或严禁用户直接传入命令!代码审计阶段我们应该多搜索下 Runtime.exec/ProcessBuilder/ProcessImpl 等关键词,这样可以快速找出命令执行点。
    `

  4. `
    java.lang.Runtime因为有一个exec方法可以执行本地命令,所以在很多的payload中我们都能看到反射调用Runtime类来执行本地系统命令,通过学习如何反射Runtime类也能让我们理解反射的一些基础用法。

    反射Runtime执行本地命令代码片段:

    // 获取Runtime类对象
    Class runtimeClass1 = Class.forName(“java.lang.Runtime”);

    // 获取构造方法
    Constructor constructor = runtimeClass1.getDeclaredConstructor();
    constructor.setAccessible(true);

    // 创建Runtime类示例,等价于 Runtime rt = new Runtime();
    Object runtimeInstance = constructor.newInstance();

    // 获取Runtime的exec(String cmd)方法
    Method runtimeMethod = runtimeClass1.getMethod(“exec”, String.class);

    // 调用exec方法,等价于 rt.exec(cmd);
    Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);

    // 获取命令执行结果
    InputStream in = process.getInputStream();

    // 输出命令执行结果
    System.out.println(IOUtils.toString(in, “UTF-8”));

    反射调用Runtime实现本地命令执行的流程如下:

    1. 反射获取Runtime类对象(Class.forName(“java.lang.Runtime”))。
    2. 使用Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor()),因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。
    3. 获取Runtime类的exec(String)方法(runtimeClass1.getMethod(“exec”, String.class);)。
    4. 调用exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))。
    `

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注