题记
在java代码执行中,通常会调用Runtime.getRuntime().exec(cmd)去执行,在Runtime.exec()方法有很多可以重载的方法。
但exec()方法最终都是调用如下:
public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
此处的cmdarray为字符串数组,即:
"cmd.exe /c calc.exe"
["cmd.exe","/c","calc.exe"]
最后都传递给new ProcessBuilder(cmdarray).start()
分别从windows和linux两个平台来跟进。
windows环境下,ProcessBuilder的start函数有多个重载,但是最终都是返回ProcessImpl.start(cmdarray,environment,dir,redirects,redirectErrorStream)
ProcessImpl的start函数有点长,应该就是读取一下环境,定义了下标准输入输出流
最终在start函数中重载了ProcessImpl构造函数
在ProcessImpl的构造函数中,先是获取了从数组中的第一个获取了程序的可执行路径,然后needsEscaping(),判断是否添加引号,createCommandLine(),判断可执行的文件是否是.CMD或者.BAT结尾来添加双引号
needsEscaping中,判断了传入是否有空格和t,有则会添加双引号
最终create执行,为native方法
Runtime.getRuntime().exec("cmd.exe /c calc.exe")
等于
new ProcessBuilder().command("cmd.exe","/c","calc.exe").start()
linux环境下ProcessBuilder的start最后仍然是ProcessImpl.start()
在ProcessImpl的start方法中,window最后返回的是重载的ProcessImpl构造函数,linux则为UNIXProcess
调用了forkAndExec
该方法也为native方法
如下命令执行例子模拟在传递字符串的情况下的拼接
linux下
Runtime.getRuntime().exec("/bin/sh -c ping dns.cn" + "&Calculator");
windows下
Runtime.getRuntime().exec("cmd.exe /c ping dns.cn" + "&calc.exe");
执行的结果为
linux下未弹出计算器
windows成功弹出计算器
在传入ProcessImpl.start()时没区别
测试原因
linux下,如果想执行成功需要加引号
windows下,引号不影响执行
如果是如下形式呢
Runtime.getRuntime().exec("mysql.exe xxxx"+"&cmd.exe /c calc,exe");
均执行不了,从上面代码根据可以看到,调用exec的只有第一个参数
String executablePath = new File(cmd[0]).getPath();
通常在代码执行利用的时候Runtime的exec都是传递字符串数组,就不存在上述问题。
System.getProperty("os.name").toLowerCase().contains("window")? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd}
exec(“cmd”)在执行时,参数都会被转成字符串数组,在ProcessBuilder中会被转成ArrayList集合。
数组中的只有第一个为程序的可执行路径,后面的均被识别为其参数。
至于用处,可以用来学习下ctf命令注入?或者实战下命令执行Runtime.exec被hook了的绕过。
监制:船长、铁子 策划:格纸 文案:青柠 美工:青柠
原文始发于微信公众号(千寻安服):Java执行命令过程即命令拼接问题
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论