影響版本:
2.0.0-2.0.9
1.0.0-1.0.5
0x00 前言
這個漏洞與之前那個SpringBoot的SpEL表達式注入漏洞點基本一樣,而且漏洞爆出來的時間點也差不多,可是沒有找到那個漏洞的CVE編號,不知道是什么原因。
這個漏洞的觸發點也是對用戶傳的參數的遞歸解析,從而導致SpEL注入,可是兩者的補丁方式大不相同。Springboot的修復方法是創建一個NonRecursive類,使解析函數不進行遞歸。而SpringSecurityOauth的修復方法則是在前綴${前生成一個六位的字符串,只有六位字符串與之相同才會對其作為表達式進行解析。然而如果請求足夠多,這種補丁也是會失效的。
0x01 調試分析
payload:
http://localhost:8080/oauth/authorize?response_type=token&client_id=acme&redirect_uri=${new%20java.lang.ProcessBuilder(new%20java.lang.String(new%20byte[]{99,97,108,99})).start()}
首先,這個漏洞是在錯誤頁觸發的,所以這里在錯誤頁的控制器打個斷點。可以看到,最后渲染視圖的時候使用了SpelView,並傳入一個模板字符串,跟springboot的洞類似。
然后跟到render方法,接收模板字符串之后,接着使用replacePlaceholders方法,對模板進行解析。
跟進一下replacePlaceholders方法
跟進parseStringValue方法,這個方法與springboot中的方法基本相同,簡單看一下。
72行進行一次遞歸,用於解析模板中類似於${${}}的結構。由於這里的模板只是一個單純的${errorSummary},故不跟進這里。
73行是將errorSummary作為參數傳入SpEL模板解析引擎
可以看到,35行將errorSummary轉換成了一個字符串,注意看這個值,其中包含我們的payload:${xxxx}。
return之后回到parseStringValue函數,將返回值賦值給propVal。
繼續跟進,到87行,這里對propVal又進行了一次遞歸解析。而propVal的值中剛好包含我們的payload(即包含“${}”)
跟進遞歸,到66行成功將我們的payload從${}中提取出來,馬上就到觸發點了
跟進到73行,將payload傳入resolvePlaceholder,繼續跟進
成功在35行將payload作為SpEL表達式解析,彈出了計算器。
0x02 補丁分析
可以看到,這里在${前加了個
RandomValueStringGenerator().generate(),用於生成一個隨機字符串。可是正如前面說的,如果可以發出足夠多的請求,那么這個補丁依舊是可以被利用的。