最近看见很多java反序列化回显的文章,用来解决内网rce不出网的情况,边学边记录一下。
因为有那么多的框架和中间件,肯定没办法面面俱到,主要关注tomcat下通用的回显方法。先整理下tomcat的架构以及运行流程,观察下可以利用的地方。
首先tomcat可以总体分为两部分:连接器和容器,对应到代码里就是Coyote和Catalina,如图:
从图中看出,客户端发送socket请求到Coyote,coyote处理后得到Request对象,CoyoteAdapter处理后封装为servlet对象传递给Catalina。细致的过程如图
所以可以直接找servlet对象,也可以找原生的Coyote的Request对象,因为servlet也是由request而来的。实际上应该是后者方便一些,因为调用链越长越复杂。
不过最早被分享的方法是直接找servlet的,是一个tomcat下半通用的思路,来自kingkk(https://xz.aliyun.com/t/7348)。
具体方法是在org.apache.catalina.core.ApplicationFilterChain找到lastServicedResponse,获取ServletResponse。可以观察下图的tomcat的web请求流程,读代码可以发现filterChain中是封装了servlet的。
测试用的springmvc,需要导入tomcat8.5的jar包,分析写在注释里了。
1 | package cn.halfblue.controller; |
注意的点主要有反射修改private static final值的方法,以及反射修改usingwriter的值,否则会爆getWriter() has already been called for this response错误,但其实不影响执行命令。
但这个方法的问题在于,service(request, response)是在所有filter执行完毕后运行的,如果执行代码的过程本身就在filter中(如shiro反序列化),那么就没办法获取到servlet对象了。
上边的方法是改变了tomcat请求的流程。另外的思路是直接找全局的response调用。这种思路就是在容器(Catalina)的处理逻辑之前找到Request对象,也就是在连接器(Coyote)内直接找processor。 后面的几种思路都是基于这个过程,不同的是获取processor的流程。
第一种方法是来自Litch1(https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3)
利用Thread.currentThread().getContextClassLoader()类加载器直接获取全局context,然后获取service下的connector–>protocolHandler–>processor–>Request。测试用的springboot(spring initializr真省事),思路写在注释里。
1 |
|
这个方法的具体调用链是WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService—>Connector—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo global—>RequestInfo——->Request——–>Response。
这个方法的问题是tomcat8以下使用不了。
下一个思路来自于c0ny1(https://paper.seebug.org/1181/)
自动化寻找,遍历所有的类。直接在中间件中挖掘request对象,然后反射获取。不过获取到的利用链都大同小异,都是获取当前所有线程然后从endpoint中获取protocolhandler
先写个简单的servlet,为了找tomcat中的链所以不用spring之类的框架,不然找到的都是框架中的。这里就写个跟他文章里一样的。
1 | public class HelloServlet extends HttpServlet { |
然后断点下在反序列化那里,模拟真实的利用过程。在evaluate中运行这些代码。注意把他写的类和tomcat的类都导入项目中,不然爆各种各样的错误。
1 | List<Keyword> keys = new ArrayList<>(); |
不同的tomcat版本跑出来的结果还不一样,虽然大同小异但还是有差别。因为不同版本用的io协议可能不同(Nio/Bio/Apr),并且handler的类型也不同。
1 | TargetObject = {org.apache.tomcat.util.threads.TaskThread} |
感觉整理一个通用的也比较困难,写了个tomcat8和9下的链。tomcat7还是不一样的地方太多了,原文作者的链是7的。
1 | public class resp8Servlet extends HttpServlet { |
其实和Litch1的后半段是一样的,区别在于获取ConnectionHandler的过程。这个工具的思路是从Thread.currentThread()出发的。
在后半段一样的基础上还有第三种方法,来自李三(https://xz.aliyun.com/t/7535)
利用注册组件直接获取processor。这个方法也是最通用的,本地单机测试tomcat789都可以。具体获取方法和代码是从参考文章里抄的,改了下打印的部分变成通用的。
1 | public class mbeanServlet extends HttpServlet { |
总的来说最后一种方法是最好的,原理简单,通用性强。
参考链接
https://xz.aliyun.com/t/7348
https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3
https://xz.aliyun.com/t/7535
https://paper.seebug.org/1181/
https://lucifaer.com/2020/05/12/Tomcat%E9%80%9A%E7%94%A8%E5%9B%9E%E6%98%BE%E5%AD%A6%E4%B9%A0