内存马系列--内存马基础

参考文章:

我们在了解内存马之前先来看看Java Web三大件&Tomcat架构

概念

内存马就是利用 反射 找到 容器核心对象,并把 恶意字节码 塞进 执行链条 的过程

内存马(Memory Shell / In-Memory Webshell) 是指攻击者通过Web应用运行时注入恶意代码,使其驻留在JVM或解释器进程内存中的一种无文件后门技术。该类攻击不依赖磁盘持久化(即无.class、.php、.py等本地文件),而是利用Java ClassLoader机制、反射调用、动态字节码生成等方式直接将恶意逻辑加载进内存并执行。

区别于传统文件型木马:

特征 文件型木马 内存马
存储位置 磁盘(如 /webapps/ROOT/shell.jsp) 运行时内存(仅存在于JVM堆/栈)
检测方式 文件完整性校验、病毒扫描引擎 行为分析、内存dump + 反编译
生命周期 系统重启后可能失效(若未自启) 随进程存在而存活,重启不丢失
可见性 易被日志、文件监控发现 极难通过常规手段定位

Java Web 三大件

在讲 Tomcat 之前,我们先讲一讲 Java Web 三大件,也就是 Servlet,Filter,Listener

当 Tomcat 接收到请求时候,依次会经过 Listener -> Filter -> Servlet

它们是 Java EE(现 Jakarta EE)规范的核心,也是所有 Java Web 容器(如 Tomcat、Jetty)运行的基础。
我们需要理解它们在内存中的驻留方式和执行顺序

Servlet

Servlet 是 Java Web 的灵魂,负责处理客户端请求并生成响应。

Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。

在 web 应用程序中的位置:

img

组成:

在 Tomcat 内存中,一个可运行的 Servlet 实际上由三个部分组成。注入内存马的过程,本质上就是用代码“伪造”并手动组装这三个零件:

  1. **Servlet 实例 (Instance)**:

    你的恶意代码类。它包含了 doGet 或 doPost,里面写着 Runtime.getRuntime().exec(cmd)。

  2. **Wrapper (StandardWrapper)**:

    这是最关键的容器节点。 Tomcat 不会直接管理 Servlet 实例,而是把每一个 Servlet 都包装在一个 Wrapper 对象中。它负责 Servlet 的加载、初始化和并发控制。

  3. **Servlet Mapping (映射关系)**:

    一张“路由表”。它告诉 Tomcat:当用户访问 /shell 这个路径时,应该去找哪一个 Wrapper。

请求的处理过程

  • 第一阶段:容器接收与对象封装(Connector 层)

    1. 客户端发起请求:例如一个 GET /shell?cmd=whoami。
    2. 监听与解析:Tomcat 的 Connector(连接器)监听到 Socket 信号,将原始字节流解析。
    3. 对象包装:容器创建 HttpServletRequest 和 HttpServletResponse。
      • 内存马细节:底层其实是 RequestFacade 对象。内存马通常需要通过反射拿到更底层的 org.apache.catalina.connector.Request 才能操作响应流。
  • 第二阶段:路径映射与“插队”检查(Container 层)

    内存马最核心的生存空间:

    1. **路由匹配 (Mapping)**:容器根据请求的 URL,去 StandardContext(Web 应用大管家)的路由表里查:这个请求该给哪个 Servlet?
      • **内存马点 (Servlet型)**:如果你已经通过代码把恶意 Servlet 塞进了这个路由表,请求就会被引向你的代码。
    2. **过滤器链创建 (Filter Chain)**:在进入 Servlet 之前,容器会临时创建一个 FilterChain。
      • 内存马点 (Filter型):这是目前最火的内存马。它会把自己插在过滤器链的最顶端。如果它在这里执行了命令并返回,你的业务 Servlet 甚至根本不会被调用。
  • 第三阶段:生命周期执行(Servlet 层)

    standard 流程:

    1. **初始化 (init)**:如果是第一次访问(或内存马刚注入),调用 init
    2. **核心调度 (service)**:容器调用 service(req, res)
      - 内存马细节:恶意 Servlet 通常直接重写 service,这样无论你发 GET 还是 POST,它都能接住。
    3. 业务分发 (doGet/doPost)**:HttpServlet 根据请求类型分发。 **
      • 内存马点:在 doGet 里写 Runtime.getRuntime().exec() 执行系统命令。
    4. 响应回传:业务逻辑将结果写入 Response 的输出流,容器负责把流数据发回给客户端。
  • 第四阶段:销毁(Lifecycle 层)

    1. **销毁 (destroy)**:容器关闭或 Web 应用卸载时调用。

内存马现状:只要服务器不重启、应用不重载,内存马就永远驻留在 JVM 内存中,“无影无踪”。

流程的代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void handleRequest(Socket socket) {
    // 1. 封装 (RequestFacade)
    HttpServletRequest req = parseRequest(socket);
    HttpServletResponse res = parseResponse(socket);

    // 2. 匹配 (查找 StandardContext 的路由表)
    ServletWrapper wrapper = context.match(req.getRequestURI());

    // 3. 实例化与初始化 (init)
    Servlet instance = wrapper.allocate(); 
    if (!initialized) instance.init();

    // 4. 过滤 (执行 FilterChain)
    FilterChain chain = context.createChain(req, wrapper);
    chain.doFilter(req, res); // 内存马常在此处!

    // 5. 服务 (service -> doGet/doPost)
    // 只有 FilterChain 走完了,才会到这里
    instance.service(req, res); 

    // 6. 销毁 (仅在卸载应用时)
    // instance.destroy();
}

servlet生命周期

  1. 初始化阶段:init() —— “出生”
  • 触发时机:默认情况下,是在第一次请求到达该 Servlet 时。如果你设置了 <load-on-startup>,则是在服务器启动时。
  • 动作:容器创建一个 Servlet 实例,并调用其 init(ServletConfig config) 方法。
  • 内存马意义:这是内存马进行“环境检查”的好地方。由于 init 在整个生命周期中只执行一次,攻击者常在这里初始化一些全局变量或解密后续要用的 Payload。
  1. 运行阶段:service() —— “工作”
  • 触发时机:每一次 HTTP 请求到达时都会调用。
  • 动作:容器接收请求,将其封装为 Request 和 Response,然后传给 service 方法。HttpServlet 里的 service 会根据请求类型(GET/POST)进一步分发给 doGet 或 doPost。
  • 内存马意义:这是内存马的核心引擎。 你的命令执行代码(Runtime.exec)通常就写在这里。只要这个 Servlet 还在内存里,每发一次请求,它就帮你执行一次命令。
  1. 销毁阶段:destroy() —— “死亡”
  • 触发时机:当 Web 应用被卸载(Undeploy)或服务器关闭时。
  • 动作:容器释放 Servlet 占用的资源。
  • 内存马意义:在实战中,只要服务器不关、应用不重载,destroy 就永远不会被执行。这也是为什么内存马被称为“无文件”木马——它只活在内存里。
  1. JVM 垃圾回收

Filter

简介

在 Java Web 架构中,Filter 是基于 Servlet 规范 的一种预处理和后处理技术。它不是一个 Servlet,它不能直接生成响应,但它可以拦截请求和响应。

其主要功能是在HttpServletRequest到达 Servlet 之前,拦截客户的HttpServletRequest ,根据需要检查HttpServletRequest,也可以修改HttpServletRequest 头和数据;在HttpServletResponse到达客户端之前,拦截HttpServletResponse ,根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

工作原理:

img

基本工作原理

  • Filter 是一个实现了 javax.servlet.Filter 接口的 Java 类。它采用责任链模式(Chain of Responsibility),多个 Filter 连在一起形成一个 FilterChain。

  • 初始化阶段(启动时):

    当 Tomcat 启动或 Web 应用加载时,容器会根据配置(web.xml 或注解)实例化 Filter。

    1. **调用 init()**:每个 Filter 只会被初始化一次。
    2. 内存驻留:初始化后的 Filter 对象会被存放在 StandardContext 的 filterConfigs(一个 HashMap)里。
  • 请求匹配与排队(请求进入时):

当一个 HTTP 请求(如 GET /index.jsp)经过了容器的路由解析后,在调用 Servlet 之前,Tomcat 会做一个关键动作:

  1. 生成 FilterChain:容器会扫描所有的 filterMaps,寻找所有路径匹配 /index.jsp 的过滤器。
  2. 确定顺序:按照配置的先后顺序,把这些 Filter 串成一根链条(ApplicationFilterChain)。
  • 过滤执行阶段(核心过关逻辑):

    这是最精彩的部分,请求必须像“闯关”一样经过每一个 Filter:

    1. 进入第一个 Filter:容器调用第一个过滤器的 doFilter(request, response, chain) 方法。
    2. 放行或拦截:
      • 放行:Filter 内部必须手动调用 chain.doFilter(request, response)。这行代码的作用是“把接力棒交给下一个保安”。
      • 拦截:如果不调用 chain.doFilter,请求就会在这里戛然而止,后面的 Filter 和 Servlet 都不会被执行。
    3. 循环往复:请求依次穿过 Filter 2, Filter 3… 直到链条结束。
    4. 到达终点:所有 Filter 都放行后,最后一个 chain.doFilter 才会真正触发 Servlet.service()。
  • 响应回溯阶段(后处理):

    当 Servlet 处理完业务后,请求并不是直接飞回客户端,而是按原路返回:

    1. 请求会回到最后一个 Filter 的 chain.doFilter 之后的代码继续执行。
    2. 最终穿过所有过滤器,由容器发回给客户端。

Filter生命周期

  1. 初始化阶段:init(FilterConfig config)
  • 触发时机:在 Web 应用启动时,或者 Filter 第一次被注入到容器时。
  • 动作:容器创建 Filter 实例,并调用 init 方法。容器会传入一个 FilterConfig 对象,里面包含了该 Filter 的配置信息(如初始化参数)。
  • 内存马意义:普通的 Filter 是在 web.xml 加载时初始化的。内存马则是通过反射调用 StandardContext 的 addFilterDef 和 addFilterMap 后,手动触发 filterConfig 的生成,从而完成“闪电上岗”。
  1. 过滤阶段:doFilter(request, response, chain)
  • 触发时机:每一次 匹配该 Filter 路径的请求进来时都会调用。
  • 动作:这是 Filter 最核心的逻辑所在。它接收 ServletRequest、ServletResponse 和 FilterChain(接力棒)。
    • 关键逻辑:如果你想让请求继续往后走,必须调用 chain.doFilter(request, response)。
  • 内存马意义:这是内存马的执行炸弹。 恶意代码就写在这里。你可以判断请求头里是否有特定的密码,如果有,就执行系统命令并直接通过 response 返回结果,不再调用 chain.doFilter,实现“截胡”。
  1. 销毁阶段:destroy()
  • 触发时机:当 Web 应用被停止、卸载,或者 Tomcat 服务器关闭时。
  • 动作:容器调用 destroy 方法,释放 Filter 占用的资源(如关闭数据库连接、清除缓存)。
  • 内存马意义:和 Servlet 一样,只要服务器不重启,内存马就不会被销毁。它是真正的“永动机”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package EvilFliter;  
  
import javax.servlet.*;  
import java.io.IOException;  
  
public class FilterTest implements Filter {  
    @Override  
    public void init(FilterConfig filterConfig) throws ServletException {  
  
    }  
  
    @Override  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
        // 在这里面进行 doGet 和 doPost 这种类似的  
 }  
  
    @Override  
    public void destroy() {  
  
    }  
}

对比Servlet &Filter:

阶段 Filter (过滤器) Servlet (业务处理器)
1. 诞生 (init) init(FilterConfig) 应用启动时全部初始化。 init(ServletConfig) 默认第一次访问时才初始化。
2. 执勤 (核心) doFilter(req, res, chain) 像安检员,必须手动放行 (chain.doFilter)。 service(req, res) 像办事员,直接处理请求并返回。
3. 结束 (destroy) destroy() 应用停止时销毁。 destroy() 应用停止时销毁。

对内存马的影响

在内存马的江湖里,Filter 内存马 远比 Servlet 内存马更受欢迎,原因有以下几点:

  1. 隐蔽性:

Servlet 必须绑定一个特定的路径(如 /shell),管理员看一眼路由表可能就发现了。但 Filter 可以配置为拦截 /*(所有路径)。

  • 影响: 攻击者不需要专门的 URL,随便访问网站的任何一个页面(如 index.php、about.html),只要在请求头里藏个暗号,Filter 就能识别并执行命令。
  1. 优先级:

    正如你之前总结的流程,FilterChain 的执行永远在 Servlet.service() 之前。

    • 影响: 如果 Filter 内存马被触发并直接输出了结果,它会直接调用 return,请求就此结束。这意味着后端的业务 Servlet 根本不会运行,也不会留下任何业务日志,非常难防。
  2. 动态注入的灵活性

    在 Tomcat 底层,Filter 的信息存储在 StandardContext 的三个对象中:

    1. **filterDefs**:Filter 的定义(名字、类名)
    2. **filterConfigs**:Filter 的实例(真正活着的对象)
    3. **filterMaps**:Filter 的路径映射(拦截哪个 URL)
  3. 对于servlet来说:

    • 监听器 (Listener)->过滤器 (Filter)->Servlet(*: Filter 内存马在 Servlet 之前就拿到了 Request 对象。)

    • Filter 拥有 FilterChain 对象

      拦截: 不调用 chain.doFilter(),后面所有的 Filter 和 Servlet 全部失效。

      放行: 调用 chain.doFilter()。会让请求继续传给后续业务。

Filter链

一个请求匹配到多个 Filter 时,Tomcat 会把这些 Filter 组织成一个有序的列表,这就是**ApplicationFilterChain**。

(类似于一串糖葫芦,每一个山楂都是一个 Filter,最后那个签子尖儿才是 Servlet。)

逻辑:每个 Filter 执行完自己的逻辑后,必须调用才能把请求传给下一个 Filter

Web 服务器根据 Filter 在 web.xml 文件中的注册顺序,决定先调用哪个 Filter。当第一个 Filter 调用chain.doFilter() ,web服务器会创建一个代表 Filter 链的 FilterChain 对象传递给该方法,通过判断 FilterChain 中是否还有 Filter 决定后面是否还调用 Filter。

img

Listener

简介

在 Java Web 三大件中,监听器(Listener)就是 Application、Session 和 Request 三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。Listener 的地位最高、触发最早、隐蔽性也最强。它不需要你访问特定路径,只要应用发生了某种“事件”,它就会自动执行。

  • ServletContextListener:对Servlet上下文的创建和销毁进行监听;
  • ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和替换;
  • HttpSessionListener:对Session的创建和销毁进行监听。Session的销毁有两种情况,一个中Session超时,还有一种是通过调用Session对象的invalidate()方法使session失效。
  • HttpSessionAttributeListener:对Session对象中属性的添加、删除和替换进行监听;
  • ServletRequestListener:对请求对象的初始化和销毁进行监听;
  • ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。

用途

可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。

**Tomcat **

简单来说,Tomcat 是一个开源的 Java Servlet 容器(也叫 Web 容器)。如果没有 Tomcat,你写的 Servlet 代码只是一个个死板的 .class 文件,根本无法处理来自互联网的 HTTP 请求。

对比Apache :

  • Apache 是 Web 服务器(静态解析,如 HTML),Tomcat 是 java 应用服务器(动态解析,如 JSP)
  • Tomcat 只是一个 servlet (jsp 也翻译成 servlet)容器,可以认为是 Apache 的扩展,但是可以独立于 Apache 运行。

就是 Web 服务器,比较不稳定,但是业务能力比较强。

在 Web 开发的架构中,Tomcat 处于 浏览器 和 你的 Java 代码 之间:

  • 对前(处理网络):它像一个服务器,监听 8080 端口,负责接收处理 TCP 连接和 HTTP 协议。
  • 对后(管理代码):它像一个管家,负责实例化你的 Servlet,并根据你总结的那套“生命周期”逻辑(init -> service -> destroy)来驱动代码运行。

Tomcat &Servlet

servlet 是规范:Servlet 容器从上到下分别是 Engine、Host、Context、Wrapper。它只是一套 Java 接口(javax.servlet.*),定义了处理请求的标准方法(如 service)

Tomcat 是实现:是 Web 应用服务器,是一个 Servlet/JSP 容器。它是一套复杂的 Java 程序,负责解析 HTTP 协议,并根据规范去调用你写的 Servlet 代码

在 Tomcat 中 Wrapper 代表一个独立的 servlet 实例, StandardWrapper 是 Wrapper 接口的标准实现类(StandardWrapper 的主要任务就是载入 Servlet 类并且进行实例化),同时其从 ContainerBase 类继承过来,表示他是一个容器,只是他是最底层的容器,不能再含有任何的子容器了,且其父容器只能是 context。而我们在也就是需要在这里去载入我们自定义的 Servlet 加载我们的内存马。

Tomcat 架构

一个 Server,多个 Service,每个 Service 包含连接器(Connector)和容器(Container)。

Tomcat 的框架如下图所示,主要有 server、service、connector、container 四个部分

如图:

img

从图片可以看出:Connector 和 Container是主要核心

Connector 主要负责对外交流,它屏蔽了底层复杂的 Socket 操作,让后面的 Container 只需处理标准的对象。,对应下图中的http服务器;

Container 主要处理 Connector 接受的请求,主要是处理内部事务,加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求,内部不是一个整体,而是采用了分层设计(套娃模式),这也就是之前提到的四个等级。(Servlet 容器) 对应下图中的 servlet 容器。

img

server

即服务器,代表整个 Tomcat 服务器,它要能够提供一个接口让其它程序能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么的。

在 Tomcat 的 conf/server.xml 配置文件里,所有的标签都被包裹在最外层的 <Server> 标签中。在内存中,它对应的是 org.apache.catalina.Server 接口(默认实现类是 StandardServer)。

Server 代表了整个 Tomcat 实例(即当前的 JVM 进程)。它管理着所有的 Service(一个 Server 可以包含多个 Service,虽然默认只有一个)。负责整个服务器的启动(Start)和停止(Stop)。持有全局的资源配置。Server 默认监听一个特殊的端口(通常是 8005),专门用来接收“SHUTDOWN”命令。

一个 Tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。

service

绑定

Service 主要是为了将一个特定的 Container(通常是 Engine)与一个或多个 Connector 关联起来,同时会初始化它下面的其它组件,在 Connector 和 Container 外面多包一层,把它们组装在一起,向外面提供服务,一个 Service 可以设置多个 Connector,但是只能有一个 Container 容器。

在 Tomcat 的 server.xml 配置文件中,<Service> 标签紧贴在 <Server> 内部。Tomcat 中 Service 接口的标准实现类是 StandardService ,它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期

  • onnector 数组:一个 Service 可以挂载多个监听不同协议、不同端口的连接器。
  • **Engine (Container)**:一个 Service 只能有一个 顶级容器(Engine)。所有的连接器最终都把请求指向这同一个“大脑”。
  • **Executor (线程池)**:Service 还可以定义共享线程池,让所有的连接器共用一套线程资源,提高效率。
  • Mapper:这是一个“导航仪”,负责根据请求的域名和路径,告诉 Service 应该把请求发给哪个 Context(应用)。

一个 Service 的本质就是:一个“大脑”(Container)配上一个或多个“耳朵”(Connector)。

Connector

翻译

Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象(把网络上的二进制数据(字节流)转换成 Java 代码能理解的 HttpServletRequest 对象。)分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。

从原理图可以看出Connector的功能:

  • socket 通信
  • 解析处理应用层协议,如将 socket 连接封装成 request 和 response 对象,后续交给 Container 来处理
  • 将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse

Tomcat 设计了三个组件,其负责功能如下:

组件 对应功能 安全研究关注点
EndPoint TCP 监听、字节流读取,将字节流传递给 Processor 协议底层劫持、无端口后门
Processor HTTP 协议解析,处理字节流生成 Tomcat Request 对象,将 Tomcat Request 对象传递给 Adapter; 畸形报文利用、Header 长度绕过
Adapter(适配器) 协议对象 -> Servlet 对象,将 Tomcat Request 对象转化成 ServletRequest 对象,传递给容器。 定位 Context 对象、获取回显 Response

Adapter 组件

由于协议的不同,Tomcat 定义了自己的 Request 类来存放请求信息,但是这个不是标准的 ServletRequest。于是需要使用 Adapter 将 Tomcat Request 对象转成 ServletRequest 对象,然后就能调用容器的 service 方法。

开始进行 Endpoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池进行处理,SocketProcessor 的 run 方法将调用 Processor 组件进行应用层协议的解析,Processor 解析后生成 Tomcat Request 对象,然后会调用 Adapter 的 Service 方法,方法内部通过如下代码将 Request 请求传递到容器中。

1
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

简单说就是

对象包装(The Wrapping) —-寻路解析(postParseRequest) —- 开启管道(Invoke) —-进入了 Engine -> Host -> Context -> Filter -> Servlet 的逻辑里面

一个总的 Tomcat Connector 功能如图所示:

img

Container

Container(又名Catalina)用于处理Connector发过来的servlet连接请求,它是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。

Tomcat 设计了 4 种容器: Engine、Host、Context、Wrapper ,这四种容器是父子关系

  • Engine: 最顶层容器组件,可以包含多个 Host。实现类为 org.apache.catalina.core.StandardEngine
  • Host: 代表一个虚拟主机,每个虚拟主机和某个域名 Domain Name 相匹配,可以包含多个 Context。实现类为 org.apache.catalina.core.StandardHost
  • Context: 一个 Context 对应于一个 Web 应用,可以包含多个 Wrapper。实现类为 org.apache.catalina.core.StandardContext
  • Wrapper: 一个 Wrapper 对应一个 Servlet。负责管理 Servlet ,包括 Servlet 的装载、初始化、执行以及资源回收。实现类为 org.apache.catalina.core.StandardWrapper

通常一个 Servlet class 对应一个 Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container。

img

每一个 Context 都有唯一的 path。这里的 path 不是指 servlet 绑定的 WebServlet 地址,而是指独立的一个 Web 应用地址。就好比 Tomat 默认的 / 地址和 /manager 地址就是两个不同的 web 应用,所以对应两个不同的 Context。要添加 Context 需要在 server.xml 中配置 docbase。

如下图所示:
在一个 web 应用中创建了 2 个 servlet 服务,WebServlet 地址分别是 /Demo1 和 /Demo2 。 因为它们属于同一个 Web 应用所以 Context 一样,但访问地址不一样所以 Wrapper 不一样。
/manager 访问的 Web 应用是 Tomcat 默认的管理页面,是另外一个独立的 web 应用, 所以 Context 与前两个不一样。

img

Tomcat 的类加载机制

由于 Tomcat 中有多个 WebApp 同时要确保之间相互隔离,所以 Tomcat 的类加载机制也不是传统的双亲委派机制。(Tomcat 自定义的类加载器 WebAppClassloader 为了确保隔离多个 WebApp 之间相互隔离,所以打破了双亲委托机制。)

eg:Tomcat 里跑了两个 Web 应用:应用 A 使用了 Spring 4.0。应用 B 使用了 Spring 5.0。
要是使用双亲委派机制的话,就会导致加载A的Spring 4.0之后,就不会再加载 5.0,因为会造成版本冲突,甚至应用崩溃。所以Tomcat 为了保证不同应用的类库互不干扰,出现了Tomcat 的类加载器

每个 WebApp 用一个独有的 ClassLoader 实例来优先处理加载。它首先尝试自己加载某个类,如果找不到再交给父类加载器,其目的是优先加载 WEB 应用自己定义的类。同时为了防止 WEB 应用自己的类覆盖 JRE 的核心类,在本地 WEB 应用目录下查找之前,先使用 ExtClassLoader(使用双亲委托机制)去加载,这样既打破了双亲委托,同时也能安全加载类。

简单说,对于 Webapp ClassLoader,它的加载顺序是:

  1. 先在 JVM 内部核心类 中找(保证基础安全)。
  2. 【反转点】:如果没找到,直接在 当前 Web 应用(WEB-INF) 下找,而不是先找父类。
  3. 如果应用下也没有,再向上委托给 Common -> System。

目的: 让应用优先使用自己带的 Jar 包,而不是被服务器全局的 Jar 包覆盖。


内存马系列--内存马基础
http://example.com/2026/04/01/内存马系列--内存马基础/
作者
Piggy Sprint
发布于
2026年4月1日
许可协议