Java下多种执行命令的姿势及问题

前言

Java 代码审计当中,关于命令执行,我们主要关注的是函数 Runtime.getRuntime().exec(command) && new ProcessBuilder(command).start()

在参数 command 可控的情况下,一般就会存在命令执行的问题,但是也会存在这种问题,有时候明明参数可控,但是无法成功执行命令,以及复杂的shell 命令,例如带有 | 、<、>、$ 等符号的命令没办法正常执行。

1
2
Command = "ping 127.0.0.1"+request.getParameter("cmd");
Runtime.getRuntime().exec(command);

这样的一段代码是存在命令注入漏洞的吗?粗略一看,存在命令执行函数,command 获取从外部传入的 cmd ,应该是存在命令注入漏洞的。但是并没有执行成功,并不存在命令执行漏洞。

1
2
3
4
5
6
7
8
9
10
import java.io.IOException;

public class linux_cmd {
public static void main(String[] args) throws IOException {
String cmd =new String(";echo 1 > 1.txt");
String Command = "ping 127.0.0.1"+cmd;
Runtime.getRuntime().exec(Command);
//Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", Command});
}
}

直接通过 Runtime.getRuntime().exec(Command); 没有成功创建 1.txt。通过 Runtime.getRuntime().exec(new String[]{“/bin/sh”, “-c”, Command}); 成功创建 1.txt。 于是尝试对 Runtime.getRuntime().exec(Command); 进行调试分析。

我们跟进 Runtime.getRuntime().exec() 发现会依据传入的参数类型,而选用不同的函数。

img

Runtime.getRuntime().exec(String command)

当传入的参数类型是 String ,会到 Process exec(String command) 这个构造方法进行处理,最后返回了 exec(command, null, null);

java.lang.Runtime#exec(java.lang.String)

img

java.lang.Runtime#exec(java.lang.String, java.lang.String[], java.io.File)

img

在这个地方我们注意到利用 StringTokenizer 对输入的 command 进行了处理

java.util.StringTokenizer#StringTokenizer(java.lang.String)

img

会根据 \t\n\r\f 把传入的 command 分割

img

java.lang.Runtime#exec(java.lang.String[], java.lang.String[], java.io.File)

img

经过处理之后,最后实例化了 ProcessBuilder 来处理传入的 cmdarray。可以证实 Runtime.getRuntime.exec() 的底层实际上也是 ProcessBuilder。

跟进 ProcessBuilder 中的 start 方法

java.lang.ProcessBuilder#start

img

ProcessBuilder.start 内部又调用了 ProcessImpl.start

img

在 ProcessImpl.start 中 将 cmdarry 第一个参数 (cmdarry[0]) 当作要执行的命令,把后面的部分 (cmdarry[1:]) 作为命令执行的参数转换成 byte 数组 argBlock。

最后将处理好的参数传给 UNIXProcess

img

java.lang.UNIXProcess#UNIXProcess

img

我们看到当前断点的 pid 是 3229 , 这里确实启动了一个 ping 进程

img

此时 prog 是要执行的命令 ping , argBlock 都是传给 ping 的参数 127.0.0.1\x00;echo\x001\x00>\x001.txt

经过 StringTokenizer 对字符串的处理,命令执行的语义发生了改变,并不是最初设定的想法。

StringTokenizer会根据空格将我们的命令划分为数组,那么我们的命令会被划分为{“/bin/sh”,”-c”,””echo”,”111”,”>”,”3.txt””},那么整个命令就变味了,达不到我们想要的效果。

Runtime.getRuntime().exec(String cmdarray[])

1
2
3
4
5
6
7
8
9
10
import java.io.IOException;

public class linux_cmd {
public static void main(String[] args) throws IOException {
String cmd =new String(";echo 1 > 1.txt");
String Command = "ping 127.0.0.1"+cmd;
//Runtime.getRuntime().exec(Command);
Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", Command});
}
}

我们传入数组,进行分析,因为直接传入的是数组,所以没有经过 StringTokenizer 对字符串的处理

java.lang.Runtime#exec(java.lang.String[])

img

java.lang.Runtime#exec(java.lang.String[], java.lang.String[], java.io.File)

img

java.lang.UNIXProcess#UNIXProcess

img

此时 prog 是要执行的命令 /bin/sh , argBlock 都是传给 ping 的参数 -c\x00”ping 127.0.0.1;echo 1 > 1.txt”

编码

编码地址

img

1
2
3
4
5
6
7
8
import java.io.IOException;

public class linux_cmd {
public static void main(String[] args) throws IOException {
String Command = "bash -c {echo,cGluZyAxMjcuMC4wLjE7ZWNobyAxID50ZXN0LnR4dA==}|{base64,-d}|{bash,-i}";
Runtime.getRuntime().exec(Command);
}
}

java.lang.Runtime#exec(java.lang.String)

img

java.lang.Runtime#exec(java.lang.String, java.lang.String[], java.io.File)

img

java.lang.UNIXProcess#UNIXProcess

img

此时 prog 是要执行的命令 bash , argBlock 都是传给 ping 的参数 -c\x00{echo,cGluZyAxMjcuMC4wLjE7ZWNobyAxID50ZXN0LnR4dA==}|{base64,-d}|{bash,-i}


Java下多种执行命令的姿势及问题
http://www.qetx.top/posts/13410/
作者
Qetx.Jul.27
发布于
2023年11月2日
许可协议