Notadmin
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

简介
在java中执行系统命令,一定会用到Runtime.getRuntime().exec,在做代码审计的过程中,遇到了传入的是一个数组的情况,第三个元素可控,这种情况下是否可以执行系统命令?
使用方法
使用exec一般有下面的两种方式
exec(java.lang.String)
只传递一个字符串,字符串代表一个没有参数的纯命令,如hostname、pwd、ll等。
exec(java.lang.String[])
字符串数组,第一个元素是命令,和exec(java.lang.String)中的一个字符串代表的概念一样,后面的元素都是命令的参数。传递数组时,第二个元素开始就是参数,因此不能直接通过&,|等连接符执行复杂的系统命令。
例如下面的字符串数组,第一个元素是执行bash文件的命令/bin/sh,也可以是bash,第二个元素是bash文件路径,第三个元素是bash文件内命令执行需要的参数。
String[] cmds = {"/bin/sh", "/root/tmp/xxx.sh", "xxx"};
不创建脚本文件执行复杂命令
如果执行的命令是一个完整的命令,不需要传参,可以直接使用exec(java.lang.String);如果执行的命令需要传递参数,则需要使用exec(java.lang.String[])。
exec(java.lang.String[])数组参数的第二个元素是可执行脚本文件路径,对于bash命令,可以不用创建文件也可以执行复杂的命令,例如:
// ps -ef | grep xxx | grep -v grep | awk '{ print $2 }'
// 过滤xxx进程并输出其pid,-v是取反过滤,排除当前命令的影响
String[] cmds = {"/bin/sh", "-c", "ps -ef | grep xxx | grep -v grep | awk '{ print $2 }'"};
Runtime.getRuntime().exec(cmds);
/bin/sh -c可将一个多操作命令合并成一个完整命令执行。
问题分析
回到最开始的那个问题,如下的代码
import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; class Main { public static void main(String[] args) throws IOException { // 创建一个动态数组 ArrayList<String> sites = new ArrayList<>(); sites.add("ffmpeg-amd64-3.0.0.exe"); sites.add("-i"); sites.add("D:\\test.txt | mkdir E:\\xixihaha111222222"); sites.add("-hide_banner"); Runtime runtime = Runtime.getRuntime(); runtime.exec((String[])sites.toArray(new String[0])); } }
调用ffmpeg-amd64-3.0.0.exe去操作文件,文件的路径可控,从exec函数的说明可以知道从第二个元素开始就被当做参数来对待,并不能去执行系统命令。实际运行下来发现也没有成功执行系统命令。
因此发现对于Java环境中的命令注入,连接符的使用存在一些局限。例如如下示例代码,使用ping命令来诊断网络。其中url参数为用户可控,当恶意用户输入“www.baidu.com&ipconfig”时,拼接出的系统命令为“ping www.baidu.com&ipconfig”,该命令在命令行终端可以成功执行。然而在Java运行环境下,却执行失败。在该Java程序的处理中,“www.baidu.com&ipconfig ”被当作一个完整的字符串而非两条命令。因此以下代码片段不存在命令注入漏洞。
protected ByteArrayOutputStream ping(String url) throws IOException { Process process = Runtime.getRuntime().exec("ping "+ url); InputStream in = process.getInputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int i = -1; while ((i = in.read(b)) != -1) { byteArrayOutputStream.write(b, 0, i); } return byteArrayOutputStream; }
总结
对于java环境的命令执行,如果传入的是一个字符串,一般要能控制字符串的开始位置才能执行,如果传入的是一个字符数组,要能控制第一个元素。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)