1 | String cmd = "ping -c 3 " + ip; |
以为是妥妥的代码执行漏洞,结果各种尝试都不成功,白激动了,分析下未成功的原因。之前也看过了各位大佬的文章,但是等到自己遇到了,还是会掉到坑里,所以还是要亲自动手。
源码调试
跟进调试,发现传入的字符串通过 StringTokenizer
分割成数组
其中 StringTokenizer
的分割依据是 \t\n\r\f
之后调用到 ProcessBuilder.start
,取出数组中除了第一个之外的元素都当作参数,并且把参数之间的空格之类的全部替换成 \u0000
最后是以命令数组的第一个元素作为命令,其他之后的元素统统作为参数来来执行命令
当然最终多个命令没有执行成功
在用数组作为参数尝试,发现也是一样,并没有成功1
2String[] command = { "ping","-c 1 8.8.8.8&&ls" };
Process myProcess = Runtime.getRuntime().exec(cmd);
因为最终追到 native
方法,没有具体的处理方法,我之前的猜测是程序把第一个命令数组的元素作为命令,其他都作为纯字符串,类似于下面这样的:
不过看到大佬的调试 JVM
,最终原因是底层方法 execvp
直接处理特殊符号时,会报错。
解决方法
现在的解决方法有2种,第一种是1
String command= "bash -c {echo,ZWNobyAxMjMgJiYgZWNobyAxMjQ=}|{base64,-d}|{bash,-i}";
第二种是1
String[] command = { "/bin/bash","-c","ping -c 1 8.8.8.8&&ls" };
都能顺利的执行多命令,可以看到都是以 bash
或者 sh
作为执行程序,真正要执行的命令作为参数,同时因为 bash
也提供了一个 shell
的上下文环境,所以连接命令的特殊字符也能得到正确解析。
其中字符串和字符串数组的区别在于,当传入字符串时,会通过 StringTokenizer
帮你分割成字符串数组,而字符串数组则无需这步。所以当使用字符串时,由于 StringTokenizer
的分割会导致一些非预期的情况,命令执行无法成功.
而我们也知道 StringTokenizer
主要根据 \t\n\r\f
来分割的,简单的说,只要不出现多余的空格,其实是和字符串数组的处理是一样的,第一种方法就是这种思路。
所以按照 linux
操作系统特性规避空格即可。类似的还有1
String command = "/bin/bash -c ping${IFS}-c${IFS}1${IFS}8.8.8.8&&ls";
或者1
sh -c $@ | sh . echo
总结
- 无论是字符串还是字符串数组,都不能直接传入多命令,需要把真正的命令作为
/bin/bash
的参数。 - 传入字符串和传入字符串数组的区别在于字符串经过了
StringTokenizer
的处理,而字符串数组则跳过了这个分割数组的部分。 类似于下面代码,不存在命令注入:
1
2String cmd = "可控";
Runtime.getRuntime().exec(cmd);