赞
踩
在传入命令时,可以传入String和String[]
String cmd1 = "/bin/sh -c whoami";
String[] cmd2 = {"/bin/sh", "-c", "whoami"};
这是由于exec()方法自带多种重载方法,其中envp指定环境,dir指定目录
public Process exec(String command)
public Process exec(String command, String[] envp)
public Process exec(String command, String[] envp, File dir)
public Process exec(String cmdarray[])
public Process exec(String[] cmdarray, String[] envp)
public Process exec(String[] cmdarray, String[] envp, File dir)
代码审计一下传入String的exec()
其实都是缺省了后几个参数,追一下完整的public Process exec(String command, String[] envp, File dir)
可以看到String command
经过StringTokenizer类实例处理后转换成了String[] cmdarray
,然后最终调用了public Process exec(String[] cmdarray, String[] envp, File dir)
,因此最终都是要转换成String[]来执行命令的。重点关注一下这里String转String[]的具体操作,首先追一下StringTokenizer的构造器
对于传入单参数,补全了两个参数又调用了三参数的重载构造器,这里加入了delimiter分隔符为空格、tab、回车换行、换行、换页符。
然后下面cmdarray的生成将String command按照默认分隔符进行切片保存,最终获得了String[] cmdarray,起一个动态调试:
得到String[]后,就到了public Process exec(String[] cmdarray, String[] envp, File dir)
,剩下就是ProcessBuilder的工作了
整个Runtime.exec(String command)的调用如下:
java.lang.Runtime.exec(String command)
java.lang.Runtime.exec(String command, String[] envp, File dir)
java.lang.Runtime.exec(String[] cmdarray, String[] envp, File dir)
java.lang.ProcessBuilder.start();
java.lang.ProcessImpl.start();
java.lang.UNIXProcess.<init>;
java.lang.forkAndExec();
然后在java.lang.UNIXProcess.<init>
构造器又调用了forkAndExec,这是一个native方法。
private native int forkAndExec;
接下来就是不同系统下调用C创建shell进程返回PID
调用链下一步是调用了它,因此也能直接用ProcessBuilder.start()
来执行命令
String[] cmd = {"/bin/sh", "-c", "ls"};
InputStream is = new ProcessBuilder(cmd).start().getInputStream();
在JDK9以后ProcessImpl与UNIXProcess合并了,因此可以视为一个东西,都是调用了native方法新建进程。
Bb动态调试一下UNIXProcess是如何实例化的:
这里我们传入的指令是/bin/sh -c ls
prog是/bin/sh
加上结束符的c字符串,也就是ascii码串;argBlock是参数串-c ls
,其中空格被视为结束符;argc是参数数量2;其他都是默认。因此,构造反射
package Test; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class ReflectionUNIXProcess { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("java.lang.UNIXProcess"); Constructor<?> constructor = cls.getDeclaredConstructors()[0]; constructor.setAccessible(true); String[] cmd = {"/bin/sh", "-c", "ls"}; byte[] prog = toCString(cmd[0]); byte[] argBlock = getArgBlock(cmd); int argc = argBlock.length; int[] fds = {-1, -1, -1}; Object obj = constructor.newInstance(prog, argBlock, argc, null, 0, null, fds, false); Method method = cls.getDeclaredMethod("getInputStream"); method.setAccessible(true); InputStream is = (InputStream) method.invoke(obj); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line = br.readLine(); while (line != null){ System.out.println(line); line = br.readLine(); } } private static byte[] toCString(String str) { byte[] bytes = str.getBytes(); byte[] result = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, result, 0, bytes.length); result[result.length - 1] = (byte) 0; return result; } private static byte[] getArgBlock(String[] cmdarray){ byte[][] args = new byte[cmdarray.length-1][]; int size = args.length; for (int i = 0; i < args.length; i++) { args[i] = cmdarray[i+1].getBytes(); size += args[i].length; } byte[] argBlock = new byte[size]; int i = 0; for (byte[] arg : args) { System.arraycopy(arg, 0, argBlock, i, arg.length); i += arg.length + 1; } return argBlock; } }
模仿ProcessImpl的操作写了两个自定义函数,运行成功
由于String转String[]的过程中是以\t\n\r\f
截断的,只要绕过这五类分隔符就能拼接指令执行:
package Test; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; public class LCE { public static void main(String[] args) throws Exception { String hacker = ";cat${IFS}resource/rw.txt"; String cmd = "/bin/sh -c whoami" + hacker; InputStream is = Runtime.getRuntime().exec(cmd).getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line = br.readLine(); while (line != null){ System.out.println(line); line = br.readLine(); } } }
这里使用${IFS}绕过空格,就能执行后面的代码cat resource/rw.txt
究其原因,是因为转换为String[]时候空格将cat的参数拆开了导致无法命令运行,而调用sh的参数-c要求传入一个完整的字符串,动态调试发现已经被拆成两个字符串了
如果用空格就会一直等待运行
Windows平台这边则无需绕过空格,直接拼接命令即可运行
字符串拆开不影响命令的完整性
欢迎在评论区留言,欢迎关注我的CSDN @Ho1aAs
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。