JAVA代码审计命令执行基础

学习JAVA代码审计,首先需要知道JAVA底层代码的逻辑,以下是相关命令执行的函数,我将以底层逻辑来进行演示。

在 Java 中可用于执行系统命令的方式有三种,分别是:

java.lang.Runtime
java.lang.ProcessBuilder
java.lang.UNIXProcess/ProcessImpl

1. java.lang.Runtime

Runtime 是 java.lang 中的一个类,主要是与操作系统交互执行操作命令。java.lang.Runtime中我们主要关注exec()方法,exec()方法有以下六种重载形式可以传入不同的数据类型的参数。

解释如下:

方法

释义

exec(String[] cmdarray)

在单独的进程中执行指定的命令和参数。

exec(String command)

在单独的进程中执行指定的字符串命令。

exec(String command, String[] envp, File dir)

在具有指定环境和工作目录的单独进程中执行指定的字符串命令。

exec(String command, String[] envp)

在具有指定环境的单独进程中执行指定的字符串命令。

exec(String[] cmdarray, String[] envp)

在具有指定环境的单独进程中执行指定的命令和参数。

exec(String[] cmdarray, String[] envp, File dir)

在具有指定环境和工作目录的单独进程中执行指定的命令和参数。

我们主要关注exec(String command)和exec(String[] cmdarray)这两种执行方式。

exec(String command)

单独的进程中执行指定的字符串命令,也就是直接执行字符串命令。

如下示例,我们输入命令后,就直接执行command的命令。

//简易示例代码
String command = "whoami";
Runtime.getRuntime().exec(command)

演示示例exec(String cmdarray)

我们创建一个JAVAWEB的项目

public void execRuntimeString(String command, HttpServletResponse response) throws IOException {
        String line = null;
        //使用 Runtime 类的 exec 方法执行给定的命令,并返回一个表示进程的 Process 对象。
        Process process = Runtime.getRuntime().exec(command);
        //创建一个 BufferedReader 对象,用于读取进程的标准输出流。这里通过 InputStreamReader 将字节流转换为字符流,并指定字符集为 GBK,以正确处理中文字符。
        BufferedReader bufferedReader =
                new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK")));
        //获取 HttpServletResponse 对象的输出流,用于向客户端发送响应。
        PrintWriter out = response.getWriter();
        //使用 while 循环逐行读取进程的输出,直到读取到末尾为止。
        while ((line = bufferedReader.readLine()) != null) {
            //逐行读取
            System.out.println(line);
            out.print(line);
        }
        //闭 BufferedReader 对象,释放资源。
        bufferedReader.close();
    }

启动之后访问相关地址:

http://127.0.0.1:8080/execRuntimeString?command=echo%20123

这里是执行输出123的命令,可以看到命令执行成功

exec(String[] cmdarray)

在单独的进程中执行指定的命令和参数。以数组的形式接收多个字符串然后执行。

示例:

String[] command = { "cmd", "/c", "whoami" };
Runtime.getRuntime().exec(command)

演示示例exec(String[] cmdarray)

我们创建一个java web的项目,示例代码如下

@RequestMapping("/execRuntimeArray")
    public void execRuntimeArray(String command, HttpServletResponse response) throws IOException {
        //定义一个字符串变量 line,用于逐行读取命令执行的结果。
        String line = null;
        //创建一个字符串数组 commandarray,其中包含了要执行的命令及其参数。这里使用了 Windows 的命令提示符(cmd.exe),通过 /c 参数告诉命令提示符执行完命令后自动退出,command 参数是从请求中传入的。
        String[] commandarray ={"cmd","/c",command};
        //用 Java 的 Runtime 类的 exec 方法执行命令数组,生成一个新的进程。
        Process process = Runtime.getRuntime().exec(commandarray);
        //创建一个 BufferedReader 对象,用于读取命令执行后产生的输出流。这里使用了指定的字符集 "GBK" 来读取输出流,通常是因为命令行默认使用GBK编码。
        BufferedReader bufferedReader =
                new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK")));
        //获取 HttpServletResponse 的 PrintWriter 对象,用于向客户端发送响应。
        PrintWriter out = response.getWriter();
        while ((line = bufferedReader.readLine()) != null) {
            //逐行读取
            System.out.println(line);
            out.print(line);
        }
        bufferedReader.close();
    }

之后访问

http://127.0.0.1:8080/execRuntimeArray?command=ipconfig

可以看到,命令执行成功

2. java.lang.ProcessBuilder

上面我们介绍了java.lang.Runtime,实际上Runtime是java.lang中的一个API,现在介绍的ProcessBuilder也是java.lang的一个API,它们的不同之处在于ProcessBulider主要用于创建操作系统进程

在java.lang.ProcessBuilder中我们要关注command()方法,可通过该方法设置要执行的命令参数。以及start()方法,简单来说使用该方法可以执行命令。

command()方法主要用于设置要执行的命令。使用start()方法可以创建一个新的具有命令,或环境,或工作目录,或输入来源,或标准输出和标准错误输出的目标,或redirectErrorStream属性的进程。新进程中调用的命令和参数有command()方法设置,工作目录将由directory()方法设置,进程环境将由environment()设置。在使用command()方法设置执行命令参数后,然后由start()方法创建一个新的进程进而在系统中执行了我们设置的命令。

我们创建一个Java web进程

    @RequestMapping("/ProcceBuilder")
    public void ProcceBuilder(String command, HttpServletResponse response) throws IOException {
        //定义一个字符串变量 line,用于逐行读取命令执行的结果。
        String line = null;
        //创建一个 ProcessBuilder 对象,用于构建新的进程。
        ProcessBuilder processBuilder = new ProcessBuilder();
        //设置命令及其参数。这里也是使用 Windows 的命令提示符(cmd.exe),通过 /c 参数告诉命令提示符执行完命令后自动退出,command 参数是从请求中传入的。
        processBuilder.command("cmd.exe", "/c", command);
        //使用 ProcessBuilder 对象的 start 方法启动进程。
        Process process = processBuilder.start();
        //创建一个 BufferedReader 对象,用于读取命令执行后产生的输出流。这里同样使用了指定的字符集 "GBK" 来读取输出流,通常是因为命令行默认使用GBK编码。
        BufferedReader bufferedReader =
                new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK")));
       //获取 HttpServletResponse 的 PrintWriter 对象,用于向客户端发送响应。
        PrintWriter out = response.getWriter();
        while ((line = bufferedReader.readLine()) != null) {
            //逐行读取
            System.out.println(line);
            out.print(line);
        }
        bufferedReader.close();
    }

接着访问以下接口

http://127.0.0.1:8080/ProcceBuilder?command=ipconfig

可以看到命令执行成功

3. java.lang.UNIXProcess/ProcessImpl

JDK9 的时候把UNIXProcess合并到了ProcessImpl当中,因此UNIXProcess和ProcessImpl可以理解本就是一个东西。

UNIXProcess和ProcessImpl最终都是调用native执行系统命令的类,这个类提供了一个叫forkAndExec的native方法,如方法名所述主要是通过fork&exec来执行本地系统命令。

UNIXProcess类是*nix系统在 java 程序中的体现,可以使用该类创建新进程,实现与”fork”类似的功能(对于Windows系统,使用的是java.lang.ProcessImpl类)

ProcessImpl 是更为底层的实现,Runtime和ProcessBuilder执行命令实际上也是调用了ProcessImpl这个类。

对于 ProcessImpl 类,我们不能直接调用需要配合使用反射。因为 java.lang.ProcessImpl 代码都被 private 封装起来了。并没有设置公共的 API 接口。

创建一个java web项目,演示代码如下

 @RequestMapping("/ProcessImpl")
    public void ProcessImpl(String command, HttpServletResponse response) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //定义一个字符串变量 line,用于逐行读取命令执行的结果。
        String line = null;
        //将传入的命令封装到一个字符串数组 cmds 中。
        String[] cmds = new String[]{command};
        //使用反射机制获取 java.lang.ProcessImpl 类的 Class 对象。ProcessImpl 是一个内部实现类,在正常使用中不应该直接访问。
        Class clazz = Class.forName("java.lang.ProcessImpl");
        //获取 ProcessImpl 类中的 start 方法,这个方法有五个参数:一个 String[] 数组,一个 Map,一个 String,一个 ProcessBuilder.Redirect[] 数组和一个 boolean。
        Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        //设置方法的可访问性为 true,即使它是私有的,这样可以通过反射调用它。
        method.setAccessible(true);
        //通过反射调用 start 方法来启动进程。传入参数分别是命令数组 cmds,null (表示没有环境变量),当前目录 ".",null (表示没有重定向) 和 true (表示要使用 ProcessBuilder 的特性)。method.invoke 的第一个参数为 null,因为 start 是静态方法。
        Process process = (Process) method.invoke(null, cmds, null, ".", null, true);
        //创建一个 BufferedReader 对象,用于读取命令执行后产生的输出流。这里同样使用了指定的字符集 "GBK" 来读取输出流,通常是因为命令行默认使用 GBK 编码。
        BufferedReader bufferedReader =
                new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK")));
        //获取 HttpServletResponse 的 PrintWriter 对象,用于向客户端发送响应。
        PrintWriter out = response.getWriter();
        while ((line = bufferedReader.readLine()) != null) {
            //逐行读取
            System.out.println(line);
            out.print(line);
        }
        bufferedReader.close();
    }

接着访问

http://127.0.0.1:8080/ProcessImpl?command=ipconfig

可以看到命令执行成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

B10SS0MS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值