Java 中几种命令执行的方式
Runtime
public class RuntimeExec {
public static void main(String[] args) throws Exception {
InputStream in = Runtime.getRuntime().exec("whoami").getInputStream();
byte[] bcache = new byte[1024];
int readSize = 0; //每次读取的字节长度
ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
while ((readSize = in.read(bcache)) > 0) {
infoStream.write(bcache, 0, readSize);
}
System.out.println(infoStream.toString());
}
}
使用 Runtime.getRuntime 来执行系统命令时,无法直接使用管道符或重定向符, 具体分析可见: Java下多种执行命令的姿势及问题 - Y4er的博客, 但可以使用 base64 编码绕过这个限制.
public static void case1_base64() throws Exception{
String encodeString = Base64.getEncoder().encodeToString(cmd.getBytes());
String cmd = "bash -c {echo," + encodeString + "}|{base64,-d}|{bash,-i}";
Runtime.getRuntime().exec(cmd);
}
如果使用 sh, 还可以使用如下的形式. 参考博客工具: Runtime.exec Payload Generater - AresX’s Blog
public static void case1_sh() throws Exception{
cmd = "ls | cat";
cmd = "sh -c $@|sh . echo " + cmd;
Process exec = Runtime.getRuntime().exec(cmd);
getOutput(exec);
}
同样的, bash 也可以使用这种形式, 其中 x 的内容可以随便写.
public static void case1_bash() throws Exception{
cmd = "ls | cat";
cmd = "bash -c $@|bash x echo " + cmd;
Process exec = Runtime.getRuntime().exec(cmd);
getOutput(exec);
}
ProcessBuilder
public class ProcessExec {
public static void main(String[] args) {
try {
InputStream in = new ProcessBuilder("whoami").start().getInputStream();
byte[] bs = new byte[2048];
int readSize = 0; //每次读取的字节长度
ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
while ((readSize = in.read(bs)) > 0) {
infoStream.write(bs, 0, readSize);
}
System.out.println(infoStream.toString());
} catch (Exception e) {
System.out.println(e.toString());
}
}
}
ProcessImpl
ProcessImpl 是更为底层的实现,Runtime 和 ProcessBuilder 执行命令实际上也是调用了 ProcessImpl 这个类,对于 ProcessImpl 类我们不能直接调用,但是可以通过反射来间接调用 ProcessImpl 来达到执行命令的目的。
public class ProcessImplExec {
public static void main(String[] args) throws Exception {
String[] cmds = new String[]{"whoami"};
Class clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, Redirect[].class, boolean.class);
method.setAccessible(true);
Process e = (Process) method.invoke(null, cmds, null, ".", null, true);
byte[] bs = new byte[2048];
int readSize = 0;
ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
while ((readSize = e.getInputStream().read(bs)) > 0) {
infoStream.write(bs, 0, readSize);
}
System.out.println(infoStream.toString());
}
}
UNIXProcess
UNIXProcess 类的构造函数会调用 native forkAndExec 函数,最终由 C 实现的 Java_java_lang_ProcessImpl_forkAndExec 函数来执行系统命令。
private native int forkAndExec(int mode, byte[] helperpath,
byte[] prog,
byte[] argBlock, int argc,
byte[] envBlock, int envc,
byte[] dir,
int[] fds,
boolean redirectErrorStream)
throws IOException;
测试代码如下:
Class<?> UnixProcess = Class.forName("java.lang.UNIXProcess");
Constructor<?> constructor = UnixProcess.getDeclaredConstructors()[0];
constructor.setAccessible(true);
byte[] argBlock = String.format("-c\00%s",cmd).getBytes();
Object o = constructor.newInstance("/bin/sh".getBytes(),
argBlock, argBlock.length,
null, 0,
null,
new int[]{-1, -1, -1},
false
);
UnixPrintService
UnixPrintService 这个类中存在很多 getter 方法可以执行系统命令
getPrinterIsAcceptingJobsAIX
例如 getPrinterIsAcceptingJobsAIX 函数,代码如下所示:
String var1 = "/usr/bin/lpstat -R " + this.printer;
String[] var2 = UnixPrintServiceLookup.execCmd(var1);
类似命令注入,只需要将 printer 设置为自己要执行的命令,然后拼接执行即可。
测试代码:
Constructor<?> constructor = ReflectUtils.getFirstCtor(UnixPrintService.class);
constructor.setAccessible(true);
UnixPrintService exp = (UnixPrintService) constructor.newInstance(";"+cmd);
Method getPrinterIsAcceptingJobsAIXMethod = UnixPrintService.class.getDeclaredMethod("getPrinterIsAcceptingJobsAIX",null);
getPrinterIsAcceptingJobsAIXMethod.setAccessible(true);
getPrinterIsAcceptingJobsAIXMethod.invoke(exp,null);
getDefaultPrintService
getDefaultPrintService 这条利用链如下:
<sun.print.UnixPrintServiceLookup: javax.print.PrintService getDefaultPrintService()>
<sun.print.UnixPrintServiceLookup: java.lang.String getDefaultPrinterNameBSD()>
<sun.print.UnixPrintServiceLookup: java.lang.String[] execCmd(java.lang.String)>
<sun.print.UnixPrintServiceLookup$1: java.lang.Object run()>
<java.lang.Runtime: java.lang.Process exec(java.lang.String[])>
UnixPrintServiceLookup
在利用 UnixPrintService 执行系统命令时最终调用的就是 UnixPrintServiceLookup.execCmd 函数,所以直接调用这个函数也可以实现命令执行。
Method execCommand = UnixPrintServiceLookup.class.getDeclaredMethod("execCmd", String.class);
execCommand.setAccessible(true);
execCommand.invoke(null,cmd);
execCmd 底层也是 Runtime.getRuntime().exec()
注意事项
Windows 环境
Runtime 和 ProcessBuilder 的底层实际上都是 ProcessImpl 。而在 Windows 下不能执行 echo 命令的原因是因为 java 找不到环境变量。所以执行命令时需要加上cmd /c
。