前言
最近在研究webshell免杀的问题,到了内存马免杀部分发现传统的Filter或者Servlet查杀手段比较多,不太容易实现免杀,比如有些工具会将所有注册的Servlet和Filter拿出来,排查人员仔细一点还是会被查出来的,所以
我们要找一些其他方式实现的内存马。比如我今天提到的JSP的内存马(虽然本质上也是一种Servlet类型的马) 。
【学习资料】
JSP加载流程分析
在Tomcat中jsp和jspx都会交给JspServlet处理,所以要想实现JSP驻留内存,首先得分析JspServlet的处理逻辑。
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
...
</servlet>
...
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
下面分析JspServlet#service方法,主要的功能是接收请求的URL,判断是否预编译,核心的方法是serviceJspFile。
public void service (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String jspUri = jspFile;
jspUri = (String) request.getAttribute(
RequestDispatcher.INCLUDE_SERVLET_PATH);
if (jspUri != null) {
//检查请求是否是通过其他Servlet转发过来的
String pathInfo = (String) request.getAttribute(
RequestDispatcher.INCLUDE_PATH_INFO);
if (pathInfo != null) {
jspUri += pathInfo;
}
} else {
//获取ServletPath和pathInfo作为jspUri
jspUri = request.getServletPath();
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
jspUri += pathInfo;
}
}
}
try {
//是否预编译
boolean precompile = preCompile(request);
//核心方法
serviceJspFile(request, response, jspUri, precompile);
} catch (RuntimeException | IOException | ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException(e);
}
}
preCompile中只有当请求参数以jsp_precompile开始才会进行预编译,否则不进行预编译。
boolean preCompile(HttpServletRequest request) throws ServletException {
String queryString = request.getQueryString();
if (queryString == null) {
return false;
}
// public static final String PRECOMPILE = System.getProperty("org.apache.jasper.Constants.PRECOMPILE", "jsp_precompile");
int start = queryString.indexOf(Constants.PRECOMPILE);
if (start < 0) {
return false;
}
queryString =
queryString.substring(start + Constants.PRECOMPILE.length());
if (queryString.length() == 0) {
return true; // ?jsp_precompile
}
if (queryString.startsWith("&")) {
return true; // ?jsp_precompile&foo=bar...
}
if (!queryString.startsWith("=")) {
return false; // part of some other name or value
}
...
}
那么预编译的作用是什么?当进行预编译后会怎么样?答案在JspServletWrapper#service中,当预编译后,请求便不会调用对应JSP的servlet的service方法进行处理,所以要想让我们的JSP能正常使用,当然是不要预编译的,默认情况下也不会预编译。
public void service(HttpServletRequest request,
HttpServletResponse response,
boolean precompile)
throws ServletException, IOException, FileNotFoundException {
Servlet servlet;
...
// If a page is to be precompiled only, return.
if (precompile) {
return;
...
/*
* (4) Service request
*/
if (servlet instanceof SingleThreadModel) {
// sync on the wrapper so that the freshness
// of the page is determined right before servicing
synchronized (this) {
``.service(request, response);
}
} else {
servlet.service(request, response);
}
...
下面再来看serviceJspFile方法,该方法判断JSP是否已经被注册为一个Servlet,不存在则创建JspServletWrapper并put到JspRuntimeContext中,JspServletWrapper.service是核心方法。
private void serviceJspFile(HttpServletRequest request,
HttpServletResponse response, String jspUri,
boolean precompile)
throws ServletException, IOException {
// 首先判断JSP是否已经被注册为一个Servlet,ServletWrapper是Servlet的包装类,所有注册的JSP servlet都会被保存在JspRuntimeContext的jsps属性中,如果我们第一次请求这个JSP,当然是找不到wrapper的。
JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
if (wrapper == null) {
synchronized(this) {
wrapper = rctxt.getWrapper(jspUri);
if (wrapper == null) {
//检查JSP文件是否存在
if (null == context.getResource(jspUri)) {
handleMissingResource(request, response, jspUri);
return;
}
//创建JspServletWrapper
w