三种命令执行的方式

  1. java.lang.Runtime#exec

    1. Runtime.getRuntime().exec("calc");
  2. java.lang.ProcessBuilder#start

    1. new ProcessBuilder().command("calc").start();
  3. java.lang.ProcessImpl#start.

由于ProcessImpl类为package-private,所以无法直接使用这个类

Java中的public,protected,package-private和private之间有什么区别? https://my.oschina.net/stackoom/blog/3136101

可以使用反射来获取该类并执行其start方法:

  1. String[] cmds = {"calc"};
  2. Class clz = Class.forName("java.lang.ProcessImpl");
  3. Method method = clz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
  4. method.setAccessible(true);
  5. method.invoke(clz,cmds, null, null, null, false);

ProcessImpl.start()

这里需要知道Runtime.exec()和ProcessBuilder.start()执行命令都是调用ProcessImpl.start来实现的.
逐步跟踪Runtime.exec可以发现它调用了ProcessBuilder.start()
image.png
继续跟踪, 发现ProcessBuilder.start又调用了ProcessImpl.start()
image.png

Runtime#exec命令切割特性

执行: Runtime.getRuntime().exec(“/bin/sh -c \”echo 111 > 3.txt\””); 发现会执行失败
逐步跟踪:
image.png
发现exec会首先执行StringTokenizer把传进去的命令字符串安装空格分割为数组, 所以/bin/sh -c “echo 111 > 3.txt” 就成了[“/bin/sh”, “-c”, “”echo”, “111”, “>”, +1 more], 数组第一个元素会被当作可执行命令,其余为参数.

Windows下执行命令要加上cmd /c , 因为Java找不到像echo这样的指令

正确的命令执行姿势:

  1. // Windows
  2. Runtime.getRuntime().exec(new String[]{"cmd","/c","echo 111 > 1.txt"});
  3. Runtime.getRuntime().exec("powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc YwBhAGwAYwA=");
  4. // Linux
  5. String[] cmd = {"/bin/sh", "-c", "echo 111 > 3.txt"};
  6. process = Runtime.getRuntime().exec(cmd);
  7. Runtime.getRuntime().exec("bash -c {echo,dG91Y2ggL3RtcC9hYmNkZWZnLnR4dA==}|{base64,-d}|{bash,-i}");

  1. String[] cmds = {"calc"};
  2. Class clz = Class.forName("java.lang.ProcessImpl");
  3. Method method = clz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
  4. method.setAccessible(true);
  5. method.invoke(clz,cmds, null, null, null, false);

jdk17以上直接用反射获取ProcessImpl类执行命令会出错.
本地测试jdk15以下无此问题,未找到具体原因.

  1. "C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3.3\lib\idea_rt.jar=11527:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3.3\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\ThinkPad\Desktop\javaSec\out\production\javatest test.CmdExcute
  2. Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make static java.lang.Process java.lang.ProcessImpl.start(java.lang.String[],java.util.Map,java.lang.String,java.lang.ProcessBuilder$Redirect[],boolean) throws java.io.IOException accessible: module java.base does not "opens java.lang" to unnamed module @3b07d329
  3. at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
  4. at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
  5. at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
  6. at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
  7. at test.CmdExcute.main(CmdExcute.java:15)
  8. Process finished with exit code 1

参考

https://y4er.com/post/java-exec-command/