0%

探探 Java Runtime.getRuntime()

在审计中,发现了如下代码

1
2
String cmd = "ping -c 3 " + ip;
Process myProcess = Runtime.getRuntime().exec(cmd);

以为是妥妥的代码执行漏洞,结果各种尝试都不成功,白激动了,分析下未成功的原因。之前也看过了各位大佬的文章,但是等到自己遇到了,还是会掉到坑里,所以还是要亲自动手。

源码调试

跟进调试,发现传入的字符串通过 StringTokenizer 分割成数组
image

其中 StringTokenizer 的分割依据是 \t\n\r\f
image

之后调用到 ProcessBuilder.start,取出数组中除了第一个之外的元素都当作参数,并且把参数之间的空格之类的全部替换成 \u0000
image

最后是以命令数组的第一个元素作为命令,其他之后的元素统统作为参数来来执行命令
image

当然最终多个命令没有执行成功
在用数组作为参数尝试,发现也是一样,并没有成功

1
2
String[] command = { "ping","-c 1 8.8.8.8&&ls" };
Process myProcess = Runtime.getRuntime().exec(cmd);

因为最终追到 native 方法,没有具体的处理方法,我之前的猜测是程序把第一个命令数组的元素作为命令,其他都作为纯字符串,类似于下面这样的:
image

不过看到大佬的调试 JVM,最终原因是底层方法 execvp 直接处理特殊符号时,会报错。
image

解决方法

现在的解决方法有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

总结

  1. 无论是字符串还是字符串数组,都不能直接传入多命令,需要把真正的命令作为 /bin/bash 的参数。
  2. 传入字符串和传入字符串数组的区别在于字符串经过了 StringTokenizer 的处理,而字符串数组则跳过了这个分割数组的部分。
  3. 类似于下面代码,不存在命令注入:

    1
    2
    String cmd = "可控";
    Runtime.getRuntime().exec(cmd);

参考文章