以初学者角度调试filter内存马
[TOC]
这片主要是以初学者角度,通过跟踪filter注解的初始化流程、以及运行时调试的方式了解filter的动态注册过程。
提前说一下注册大致流程,通过request对象获取StandardContext对象,然后设置以下三个变量:
存放每个filter的类的位置:org.apache.catalina.core.StandardContext#filterDefs
存放每个filter的url映射:org.apache.catalina.core.StandardContext#filterMaps
存放每个filter的配置,构建filterChain用到的变量:org.apache.catalina.core.StandardContext#filterConfigs
filter的注册、运行主要涉及以上三个变量。
filter的注解初始化
跟踪一下filter的注解是怎么初始化的,看看把filter的相关信息存放到什么位置。
先自己写一个过滤器MyFilter,用注解的方式声明名字以及url路径
@WebFilter(filterName = "MyFilter", urlPatterns = "/*")
搜索这个注解的处理位置
这里感谢@520大佬的指点
找到了processClass方法
根据这个if语句判断,继续往下跟踪这个函数
org.apache.catalina.startup.ContextConfig#processAnnotationWebFilter
这里面就是处理filter注解的整个流程
主要是围绕filterDef、filterMap这两个对象展开处理的
对于filterDef,设置了名字以及类路径
最后这个对象就会被添加到fragment中
对于filterMap,设置了filter名、url映射、Dispatcher方式
最后这个对象也会被传入fragment对象中
那么这个fragment对象又是什么呢?
通过层层向上回溯,发现是个webXml对象,里面存放着web的各种配置信息,会和web.xml读取出来的信息会进行合并
最后会通过configureContext函数解析webXml对象中的数据
org.apache.catalina.startup.ContextConfig#configureContext
以下就是解析filter的代码
那这个数据存放到哪了呢?
通过调试可以看到是StandardContext类型的context对象
一般的话Context就是上下文对象,是个不变的对象,相当于这个数据就永久存放到了这个对象中
看一看StandardContext类型对象调用的addFilterDef方法,发现底层其实就是存放到了filterDefs对象数组中
addFilterMap函数同理,存放到了filterMaps对象中
总结:通过web.xml配置和我们的WebFilter注解的方式,最终把我们的filter所有信息都封装成filterDef、filterMap这两个对象,然后最终都存放到了StandardContext类型对象的filterDefs变量和filterMaps变量中。
这里就产生了一个思路,只要我们获取到StandardContext这个上下文对象,把我们的filter包装成filterDef、filterMap对象,然后通过函数或者反射方式加到StandardContext的两个变量中,就相当于走完以上所有的流程配置了filter。
filter运行时的调试
只通过filter的加载初始化还不能说明我们就立马能用filter了,还需要运行调试,看看filter到底是怎么调用的。
在我们的自定义MyFilter的doFilter方法上打断点,这是我们的调用栈
先从涉及到的Filter的上层开始
org.apache.catalina.core.StandardWrapperValve#invoke
这里很直接的直接调用用的filterChain的doFilter方法
filterChain是什么呢?往上看,是个ApplicationFilterChain类型的对象,这个对象是个重点。
那么这个filterChain对象里面是什么呢?
可以看到filterConfig的filters变量就是ApplicationFilterConfig类型,这里面存放着filter的各种配置。而且我们发现这个n就是filter的个数,里面有两个,第0个就是我们的MyFilter
继续看下一个栈
org.apache.catalina.core.ApplicationFilterChain#doFilter
这里的doFilter主要工作就是过滤特殊请求,继续看下一个栈
org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
这里看到,从filterConfig中直接取到了我们的Myfilter对象,所以我们知道了,我们filter对象存放在了filters这个变量中,而且这个变量是ApplicationFilterConfig类型数组。
那么,这个filters变量什么时候初始化的?
通过追踪变量,找到了ApplicationFilterChain#addFilter方法,这个就是用来给filters变量赋值的方法
继续追踪,找哪里调用了这个addFilter方法
最终追踪到了这个createFilterChain方法
先说一下结论:这个方法会把符合要求的filter添加到filterChain中,也就是添加到filterChain内部的filters变量中。
org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
这个是个有关filter的十分关键的方法,每次HTTP请求都会创建一个filter链,而这个方法就是创建filter链(filterChain)的过程。
不是所有的filter都会在链中,会把符合特定条件的filter添加到链中
创建filterChain的过程中,这里会遍历StandardContext中的filterMaps,并把filter中的url映射与请求路径做对比,如果匹配就会
从context(StandardContext对象)中查询这个对应filter的filterConfig对象,添加到filterChain中。
等等,从StandardContext中查询这个对应filter的filterConfig对象?意思是这个context还存放着filterConfig对象,我们还遗漏了这个东西。截止到目前我们知道了需要在context中存放filterDef、filterMap、filterConfig对象。
那么是什么地方初始化了filterConfig这个对象呢?这里不展开讲了,先说结论
org.apache.catalina.core.StandardContext#filterStart方法把filterDefs都转换为filterConfig存放到filterConfigs变量中
继续跟着上文
从filterConfig获取到了我们filter的类,所以我们注册一个filterConfig,让getFilter返回我们的filter即可。
但是我们发现这个getFilter方法其实内部还是用了FilterDef
所以说先创建filterDef,才能再创建filterConfig对象
总结一下以上filterChain的创建以及运行的过程
- 通过ApplicationFilterFactory#createFilterChain函数把符合要求的filter加入到filterChain并返回
- 调用filterChain的doFilter方法,内部再调用internalDoFilter方法
- 在internalDoFilter方法中,首先获取filetChain变量中存放的的第一个filterConfig对象
- 通过filterConfig对象获取filter对象(这里是我们的MyFilter对象),然后调用dofilter方法
- 如果我们的方法中又调用了doFilter方法,那么会重复第3步,获取到第二个filterConfig对象,以此类推,直到获取完所有的filterConfig
以下是filterChain的获取以及调用的时序图(图片来源忘了...)
动态注册filter的代码编写
到这里我们就有基本思路了,把我们的filter封装成FilterDef、FilterMap、FilterConfig三个对象,传入到StandardContext对象的FilterDefs、FilterMaps、FilterConfigs变量中
FilterDef -> FilterDefs
FilterMap -> FilterMaps
FilterConfig -> FilterConfigs
最简单粗暴的方法就是通过反射设置这三个变量,但是这样代码过于臃肿。
我们可以找间接设置的这些变量的函数,以下就是我找到的相对较短的代码,过程就不赘述了。
以下省略了创建Filter的代码
protected void service(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
String name = "simpleFilter";
try {
// 获取standardContext对象
ServletContext requestServletContext = request.getServletContext();
ApplicationContext applicationContext = (ApplicationContext) getFieldValue(requestServletContext, "context");
StandardContext standardContext = (StandardContext) getFieldValue(applicationContext, "context");
// 注册FilterDefs
FilterDef filterDef = new FilterDef();
filterDef.setFilter(new HackFilter());
filterDef.setFilterClass(HackFilter.class.getName());
filterDef.setFilterName(name);
standardContext.addFilterDef(filterDef);
// 注册FilterMaps,通过addMappingForUrlPatterns(),在之前必须添加filterDefs,否则会报错
ApplicationFilterRegistration applicationFilterRegistration = new ApplicationFilterRegistration(filterDef, standardContext);
applicationFilterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, new String[]{"/*"});
// 第二个参数为false,调用addFilterMapBefore,filter优先级提高
// 注册FilterConfigs
standardContext.filterStart();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
HackFilter的核心代码
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("HackFilter entry...");
chain.doFilter(request, response);
System.out.println("HackFilter exit...");
}
测试:
首先正常访问
访问Servlet动态注册filter后,然后再次访问网站,控制台输出如下,可以看到filter注册成功,而且优先级是第一
以下是我找到的用于设置filterDef、filterMap、filterConfig变量到StandardContext的函数
注册filterDef的方法
第一种:调用org.apache.catalina.core.StandardContext#addFilterDef函数
// filterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(new HackFilter());
filterDef.setFilterClass(HackFilter.class.getName());
filterDef.setFilterName(name);
standardContext.addFilterDef(filterDef);
第二种:直接对filterDefs变量操作,filterDefs.put(filterDef.getFilterName(), filterDef);
// filterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(new HackFilter());
filterDef.setFilterClass(HackFilter.class.getName());
filterDef.setFilterName(name);
Map<String, FilterDef> filterDefs = (Map<String, FilterDef>) getFieldValue(standardContext, "filterDefs");
filterDefs.put(filterDef.getFilterName(), filterDef);
注册filetrMap的方法
第一种:调用org.apache.catalina.core.StandardContext#addFilterMap方法
可以将addFilterMap函数换成
addFilterMapBefore(filterMap);
可以在filter最前面插入,优先级提高
// FilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(name);
filterMap.setDispatcher("REQUEST");
filterMap.addURLPattern("/*");
standardContext.addFilterMap(filterMap);
// standardContext.addFilterMapBefore(filterMap);
第二种:通过ApplicationFilterRegistration的通过addMappingForUrlPatterns方法
在之前必须添加filterDefs,否则会报错
eg:
// 通过addMappingForUrlPatterns(),设置FilterMap,在之前必须添加filterDefs,否则会报错
ApplicationFilterRegistration applicationFilterRegistration = new ApplicationFilterRegistration(filterDef, standardContext);
applicationFilterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, new String[]{"/*"});
// 第二个参数为false,调用addFilterMapBefore,filter优先级提高
第三种:反射获取filterMaps变量,然后调用filterMaps.add(filterMap)
// FilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(name);
filterMap.setDispatcher("REQUEST");
filterMap.addURLPattern("/*");
// 调用底层的addBefore方法
Class<?> aClass = Class.forName("org.apache.catalina.core.StandardContext$ContextFilterMaps");
Object filterMaps = getFieldValue(standardContext, "filterMaps");
Method addBefore = aClass.getMethod("addBefore", new Class[]{FilterMap.class});
addBefore.setAccessible(true);
addBefore.invoke(filterMaps, new Object[]{filterMap});
第四种:最底层操作,反射获取filterMaps内部类的array变量,直接对变量操作
// FilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(name);
filterMap.setDispatcher("REQUEST");
filterMap.addURLPattern("/*");
// 获得filterMaps变量
Object filterMaps = getFieldValue(standardContext, "filterMaps");
// 获得内部类array、insertPoint变量
FilterMap[] array = (FilterMap[]) getFieldValue(filterMaps, "array");
Integer insertPoint = (Integer) getFieldValue(filterMaps, "insertPoint");
// 把filter添加到最前面
FilterMap results[] = new FilterMap[array.length + 1];
System.arraycopy(array, 0, results, 0, insertPoint);
System.arraycopy(array, insertPoint, results, insertPoint + 1,
array.length - insertPoint);
results[insertPoint] = filterMap;
array = results;
insertPoint++;
setFieldValue(filterMaps, "insertPoint", insertPoint);
setFieldValue(filterMaps, "array", array);
注册filterConfig的方法
第一种:通过StandardContext#filterStart方法从filterDef 自动添加到 filterConfigs变量中
前提是已经注册filterDef到了filterDefs变量中
org.apache.catalina.core.StandardContext#filterStart
可以将filterDefs变量批量注册到filterConfigs中
⚠️但是可能有点风险,会清除之前的filterConifgs,然后重新创建filterConfigs
// 注册FilterConfigs
standardContext.filterStart();
第二种:直接修改FilterConfigs变量。通过反射获取FilterConfigs变量,然后反射创建ApplicationFilterConfig对象,put方法添加进去。
eg:
// 反射获取filterConfigs变量
Map<String, ApplicationFilterConfig> filterConfigs = (Map<String, ApplicationFilterConfig>) getFieldValue(standardContext, "filterConfigs");
// 创建applicationFilterConfig对象
Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(new Class[]{Context.class, FilterDef.class});
declaredConstructor.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) declaredConstructor.newInstance(new Object[]{standardContext, filterDef});
// 添加到filterConfigs中
filterConfigs.put(name, applicationFilterConfig);
结尾
如何获取StandardContext、web.xml的解析过程这里没有展开讲,可以自己调试一遍。
总结一下,通过跟踪注解的初始化流程,把filter的信息封装成filterDef、filterMap对象分别存放到了StandardContext的filterDefs、filterMaps中。
再通过调试filter运行的过程,了解了创建filterChain的过程:通过filterMap对象判断是否加入到这个filterChain的filters变量中,运行时遍历filters,从而调用filter的doFilter方法。
之后根据涉及到的三种filter对象编写出对于动态注册的代码。