[Java代码审计]—命令执行失效问题

文章探讨了Java中命令执行的不同方式,强调了使用字符串数组代替字符串的优势,特别是在处理转义字符如` `时。通过单例模式的解释,说明了为何使用`Runtime.getRuntime().exec()`而非直接`Runtime.exec()`。文章还介绍了`ProcessBuilder`在执行命令中的作用,并展示了如何通过反射调用`start()`方法。最后,文章提到了命令行参数的处理问题,特别是当命令包含逻辑操作符如`&&`时,使用字符串数组能避免解析错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

关于Java的命令执行其实一直都没有单独学习过,正好昨天师傅问了一个问题:命令执行时字符串和字符串数组用哪个更好一些。当时被问得有点懵难道不都一样么?其实不然,借此重新了解下RCE以及失效问题。

单例模式

常规命令执行代码:

Runtime.getRuntime().exec("calc");

在看过Runtime类后,一直有个问题:exec()就在Runtime类中,为什么不直接Runtime.exec()进行调用,而是中间加上getRuntime()?

在这里插入图片描述

这个就是由单例模式决定的:

单例模式(Singleton)的目的是为了保证在一个进程中,某个类有且仅有一个实例。

因为这个类只有一个实例,因此,自然不能让调用方使用new Runtime()来创建实例了。所以,单例的构造方法必须是private,这样就防止了调用方自己创建实例,但是在类的内部,是可以用一个静态字段来引用唯一创建的实例的,这个实例也就是getRuntime()

private static Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {
    return currentRuntime;
}

所以我们在获取Runtime实例时,就要通过getRuntime()方法获取。

流程分析

exec()有以下几个重载方法,根据不同形参进行调用

public Process exec(String command)-----在单独的进程中执行指定的字符串命令。
public Process exec(String [] cmdArray)---在单独的进程中执行指定命令和变量
public Process exec(String command, String [] envp)----在指定环境的独立进程中执行指定命令和变量
public Process exec(String [] cmdArray, String [] envp)----在指定环境的独立进程中执行指定的命令和变量
public Process exec(String command,String[] envp,File dir)----在有指定环境和工作目录的独立进程中执行指定的字符串命令
public Process exec(String[] cmdarray,String[] envp,File dir)----在指定环境和工作目录的独立进程中执行指定的命令和变量

先看下大致流程,跟进exec(),会调用StringTokenizer()把传入的conmmand字符串按中的 \t \n \r \f 分割成数组cmdarray。(为什么数组更好原因就在这里,等会在看)

在这里插入图片描述

public StringTokenizer(String str) {
    this(str, " \t\n\r\f", false);
}

分割后,由于cmdarray变成了数组,便会进入其他有参exec()方法,其中实例化了ProcessBuilder,并调用了start()方法,这里的cmdarray的值就是我们传入的calc,其中environment、directory都是对值进行初始化,主要还是start()这里

在这里插入图片描述

跟进后又调用了,process实现类的start(),执行了calc(其中调用过程牵扯到linux的UNIXProcess,而windows中没有所以就不跟进了)

在这里插入图片描述

ProcessBuilder

根据上边的流程可以发现:真正执行代码的地方其实是:

new ProcessBuilder(cmdarray)
            .environment(envp)
            .directory(dir)
            .start();

所以除了常规的Runtime.getRuntime().exec()可以换成

new ProcessBuilder("calc").start();

反射调用:

Class<ProcessBuilder> pbc = ProcessBuilder.class;
Constructor<ProcessBuilder> cons = pbc.getDeclaredConstructor(String[].class);
Method method = pbc.getDeclaredMethod("start");
Object o = cons.newInstance(new String[][]{{"calc"}});
method.invoke(o);

失效问题

最后看下失效问题,这里失效主要是使用String类型导致,这里把calc命令换成echo Sentiment && echo 6666

正常情况下&&会当做连接符,执行下一个命令

在这里插入图片描述

但是在java如果用String类型命令,执行后的结果变成了Sentiment!&&echo 123,也就是&&也当作字符串进行输出了

在这里插入图片描述

原因也就在于StringTokenizer方法的处理,将后边的&echo其解析成了一段字符

在这里插入图片描述

所以这里就需要用字符串数组类型,来解决这个问题,此时将命令换为:

String[] cmd = {"cmd","/c","echo Sentiment!&&echo 123"};
#liinux
String[] cmd = {"/bin/bash","-c","echo Sentiment!&&echo 123"};

执行后得到了我们想要的结果:

在这里插入图片描述

再看下代码,由于直接用了数组,因此在执行了exec()时,直接执行到了形参为数组的exec()方法中,直接执行ProcessBuilder.start()方法中,成功执行命令

在这里插入图片描述

因此在遇到命令中有\t\n\r\f这类转义字符时,需要用字符串数组类型。其实除了这种方式外还可以通过对命令进行编码的形式解决,不过只针对于linux

在这里插入图片描述

参考

Java Runtime.getRuntime().exec由表及里 - 先知社区 (aliyun.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值