Java命令执行浅析

本文深入探讨了Java中执行命令的三种方式:Runtime、ProcessBuilder和ProcessImpl,详细分析了动态调试过程,并解答了关于命令执行的常见疑惑,包括参数类型、字符解析和cmd /c的使用时机。通过实例解析,揭示了Java命令执行的内部工作机制。

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

Java命令执行浅析

一、Java执行命令的几种方式

1、java.lang.Runtime

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

在这里插入图片描述
主要分为两大类:

第一类入参为String类型

第二类入参为String[]类型


2、java.lang.ProcessBuilder

ProcessBuilder pb = new ProcessBuilder("whoami");
pb.start();
new ProcessBuilder("cmd", "/c", "net user").start();
new ProcessBuilder(list).start();

在这里插入图片描述
在这里插入图片描述

构造方法如下两类:

public ProcessBuilder(String... command) 
public ProcessBuilder(List<String> command)



3、java.lang.ProcessImpl

Class clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, 				String.class, ProcessBuilder.Redirect[].class, boolean.class);

method.setAccessible(true);
method.invoke(null, new String[]{"calc"}, null, null, null, false);

该类非Public修饰,所以在不同包下只能通过反射的方式去调用执行。




二、动态调试

跟进java.lang.Runtime类的调用过程:

调用Runtime.getRuntime()方法返回currentRuntime静态成员对象,
在这里插入图片描述



继续调用exec方法,这里exec方法重载,有多种同名不同参数类型方法,我们跟进参数为 (String command)的exec方法,继续往下走(exec方法返回类型均为Process抽象类):
在这里插入图片描述




进入public Process exec(String command, String[] envp, File dir) 方法:在这里插入图片描述




实例化StringTokenizer类,将命令执行command参数传入,实现构造方法,会将特殊字符打上标识。

在这里插入图片描述




经过StringTokenizer类处理后,会得到一个cmdarray数组(根据空格进行切割命令)

在这里插入图片描述




进入exec(cmdarray, envp, dir)方法,发现return new ProcessBuilder(cmdarray).start();方法

在这里插入图片描述



跟进start()方法
在这里插入图片描述




将cmdarray数组第一位赋值给字符串 prog,经过SecurityManager安全管理类校验,默认返回NULL,跳出if判断,判断传入内容是否存在00,存在抛出异常结束运行,继续向下执行
在这里插入图片描述




发现ProcessImpl.start方法,这里我们可以得出结论,以上三种Java执行命令的过程,都是通过实现ProcessImpl.start来实现的,继续跟进,进入ProcessImpl构造方法

private ProcessImpl(String cmd[],
                    final String envblock,
                    final String path,
                    final long[] stdHandles,
                    final boolean redirectErrorStream)

在这里插入图片描述




SecurityManager类进行安全校验,通过变量 allowAmbiguousCommands 判断是否允许调用本地进程,赋值allowAmbiguousCommands 变量为 true,进入if条件

当系统允许调用本地进程时,进入Legacy mode(传统模式),会调用needsEscaping(),当prog存在空格且未被双引号包裹时需要使用quoteString()进行处理,接着调用createCommandLine()将命令数组再次拼接为字符串,最后调用native方法(java调用非java代码的接口)create()创建进程,成功执行命令。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述





三、Java执行命令常见疑惑

通过上述调试,我们可以得出一个结论,常见的命令执行方式均是通过一种手法来实现命令执行,即 通过ProcessImpl类实现。


1、不同方式执行命令传入的command参数,String、 String[]、List?

通过上述调试,我们知道,当传入的参数如果是String字符串,会在处理过程中,被处理为String[]数组,在最底层的调用过程中又会转换为String字符串进行拼接(windows下)。

而我们直接通过ProcessBuilder类执行命令时。可以传入List集合,但List集合已被泛型String约束,所以理论上也是传递的一个数组

所以我们传递的参数都是会被转换为数组类型便于进行校验、取值和判断



如,在ProcessBuilder类中start()方法中数组第一位存储的数值被SecurityManager类校验,随后遍历数组每一位是否存在\x00字符等类似情况
在这里插入图片描述



2、传入命令执行失败,字符无法解析?

linux命令行执行:

/bin/sh -c echo 111 > 3.txt

在这里插入图片描述



/bin/sh -c "echo 111 > 3.txt"

在这里插入图片描述




通过Java命令执行

在这里插入图片描述




经过StringTokenizer这个类进行拆分之后变成了**{"/bin/sh","-c","“echo”,“111”、">",“3.txt”"}**

StringTokenizer类对传入的命令进行了破坏,究其原因是因为该类以空格来分割命令字符串

在这里插入图片描述




command参数直接为数组时
在这里插入图片描述




调试,直接进入构造方法,不再需要经过StringTokenizer类处理,从而获得了正确的数组

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

String[] command = { “/bin/sh”, “-c”, “echo 111 > 3.txt” };

在这里插入图片描述




同理类似

ls "My Directory"会被解释为ls '"My' 'Directory"'

``ls > dir_会被解释为获取>dir_listing`目录的列表。

重定向和管道字符的使用方式在正在启动的进程的上下文中没有意义

解决方案:直接传递数组类型,或通过Base64编码,且还确保参数中没有空格影响。

String cmds = "bash -c {echo,base64==}|{base64,-d}|{base64,-i}";





3、Java命令执行什么时候写 cmd /c ?

通过上面的调试,最终通过create native函数调用,

根据JNI命名规则,会调用到ProcessImpl_md.c (jdk8u-jdk)中的Java_Java_lang_ProcessImpl_create()函数,该函数会调用processCreate函数,最终调用Windows系统API函数:CreateProcessW(),用来创建一个新的Windows进程。创建成功后,将新进程的句柄返回给ProcessImpl#create()。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述



偶尔有时命令执行有效负载Runtime.getRuntime().exec()会失败。使用Webshell,反序列化漏洞或其他向量时可能会发生这种情况。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述




通俗的讲就是,

当第一个参数(lpApplicationName)为Null时,第二个参数lpCommandLine需要提供启动程序及所需参数,彼此间以空格隔开。

即默认写法第lpApplicationName参数为cmd.exe lpCommandLine参数为/c 如果没有,就把传递的第一个参数当做环境变量中存在的.exe文件调用。

如图所示,当执行类似ping whoami命令时,可以省略 cmd 写法

在这里插入图片描述



/c参数,若不指定/c参数,cmd进程将不会关闭,java程序会一直阻塞,当指定/c时,cmd进程会在命令执行完毕后成功终止。

在这里插入图片描述



整体流程图,如下所示,暂未调试linux下命令执行过程,有待完善,文章借鉴了很多blog,如有错误,还望斧正:)

在这里插入图片描述






参考链接:

https://mp.weixin.qq.com/s/V16es4xvwfJ7owJqPqvNxQ

https://mp.weixin.qq.com/s?__biz=MzI0NzEwOTM0MA==&mid=2652471913&idx=1&sn=f8c29997df75dbe910be431e5beb2faa&scene=21#wechat_redirect

https://www.freebuf.com/articles/web/236925.html

https://blog.spoock.com/2018/11/07/java-reverse-shell/

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa

OTM0MA==&mid=2652471913&idx=1&sn=f8c29997df75dbe910be431e5beb2faa&scene=21#wechat_redirect

https://www.freebuf.com/articles/web/236925.html

https://blog.spoock.com/2018/11/07/java-reverse-shell/

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值