浅析利用Tomcat ApplicationFilterChain类实现半通用回显
/0x00 前言
之前没搞过这种半通用回显方法,学习一下。
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原理图:
而ApplicationFilterChain类则是FilterChain接口类的实现类。
ApplicationFilterChain类
简介
Tomcat中的ApplicationFilterChain类是一个Java Servlet API规范javax.servlet.FilterChain接口类的实现类,用于管理某个请求request的一组过滤器Filter的执行。当针对一个request所定义的一组过滤器Filter处理完该请求后,最后一个doFilter()调用才会执行目标Servlet的service()函数。之后响应对象response会按照相反的顺序依次经过这组Filter处理,最终到达客户端。
类图
创建过程
ApplicationFilterChain类是在StandardWrapperValve类中invoke()方法中调用ApplicationFilterFactory.createFilterChain()方法创建的。StandardWrapperValve是Wrapper的标准阀,用在Pipleline流程中的最后一个valve执行,其中会创建ApplicationFilterChain对象并调用其doFilter()方法来处理请求,这个ApplicationFilterChain包含着配置的与请求相匹配的Filter和Servlet,其doFilter()方法会依次调用所有的Filter的doFilter()方法和Servlet的service()方法。
这里可看源码注释分析:
1 | public final void invoke(Request request, Response response) |
跟进看下ApplicationFilterFactory类的createFilterChain()函数的具体实现:
1 | public static ApplicationFilterChain createFilterChain(ServletRequest request, |
源码浅析
这里直接从ApplicationFilterChain类源码中看注释分析:
1 | /** |
看完代码,我们可以简介归纳一个ApplicationFilterChain对象包含几个主要参数:
- n:filter个数;
- pos:下一个要执行的filter的位置;
- Servlet:当pos=n即过滤完成时,调用Servlet的service()方法,把请求交给Servlet;
- filters:Filter的相关配置信息;
所以,ApplicationFilterChain对象的执行其实就是通过pos作为索引来逐个执行设置的filter的doFilter()函数,执行完所有filter的doFilter()后,就会调用Servlet的service()函数来处理请求。
0x02 回显利用
基本原理
一般的,基于Tomcat的回显利用实现思路如下:
- Tomcat中存在保存Request和Response的某些变量;
- 通过读取Request对象来获取输入的命令参数;
- 通过写入Response对象来实现响应回显输出;
而在前面分析的ApplicationFilterChain类中,我们看到在调用internalDoFilter()函数时,Request和Response是保存到lastServicedRequest和lastServicedResponse变量中的:
1 | // We fell off the end of the chain -- call the servlet instance |
而这两个变量是由private static final修饰的ThreadLocal类型的变量,static使得可以直接在没有创建对象的情况下来获取变量,ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改
:
1 | // Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1 |
下面具体看看怎么实现回显。
代码实现
本地起个简易的Spring Boot,其中Controller代码如下,尝试获取当前HttpServletResponse内容和输入的input参数:
1 |
|
调试看调用栈,该Response实例在函数调用栈中始终是同一个传递下来的:
这里理一下,我们的目标是要在本Controller的响应中添加命令执行的回显到页面中。
在前面的源码浅析中,我们知道ApplicationFilterChain这个类的internalDoFilter()函数中,会将当前的ServletRequest和ServletResponse保存到其成员变量lastServicedRequest和lastServicedResponse中,当然前提是ApplicationDispatcher.WRAP_SAME_OBJECT为true(默认为false):
看到WRAP_SAME_OBJECT是个static final变量,问题不大,可以通过反射来修改其值为true,从而使得程序能跑到if逻辑中让当前的ServletRequest和ServletResponse保存到lastServicedRequest和lastServicedResponse中。
具体怎么修改可以参考网上的文章:利用反射修改final数据域
先尝试写了下,代码说明如注释:
1 |
|
直接跑之后控制台报如下错:
原因在于执行完Servlet的service()函数修改了WRAP_SAME_OBJECT为true后会进入下面的finally逻辑,其中会设置lastServicedRequest和lastServicedResponse两个变量值为null,但是这两个变量在默认情况下是null、并没有被新建:
根据这个错误信息,我们需要在代码中也新建lastServicedRequest和lastServicedResponse这两个实例,这样代码就没问题了。同时,还得加入获取URL参数并执行的代码,最后添加到Response中回显输出。看代码注释即可:
1 |
|
如代码所示,访问两次就出回显了:
半通用的原因
利用Tomcat ApplicationFilterChain类实现回显利用的方式之所以说是半通用,这是因为在Shiro中并不可行。原因在于,ApplicationFilterChain类中Request和Response的设置是在Shiro反序列化漏洞触发点之后。
这里看到在调用ApplicationFilterChain类的internalDoFilter()函数时,调用到了Shiro的ShiroFilter过滤器类、其中会调用到CookieRememberMeManager类的getRememberedSerializedIdentity()函数来获取cookie内容并进行反序列化操作,这个过程都是还在调用应用的doFilter()的时候就触发了,而间于Filter Chain执行之后、调用Servlet实例之前的Request和Response的设置就不起作用了: