【最新漏洞預警】CVE-2022-22965 Spring核心框架Spring4Shell遠程命令執行漏洞原理與修復方式分析
漏洞信息
Spring是目前全球最受歡迎的Java輕量級開源框架。近日網上爆出Spring核心框架存在RCE漏洞(編號CVE-2022-22965)。在野曝光一段時間后,與近幾年流行的高危漏洞命名方式類似(比如ProxyShell、log4jShell等),這個漏洞被稱為Spring4Shell。3月31日官方終于發布了漏洞信息,并在新版本v5.3.18和v5.2.20中修復了漏洞。(官方不發補丁我也不敢公開發布文章呀!)
分析后發現漏洞結合了JDK9及以上版本一個新的屬性,成功繞過歷史漏洞CVE-2010-1622修復補丁,同時結合Tomcat容器的一些操作屬性,可以實現GetShell。當然Weblogic、Jetty等其他Java中間件或應用程序也可能構建出完整利用鏈,但從目前研究進度來看,漏洞觸發需要至少滿足以下條件:
- JDK9或以上版本系列
- Spring框架或衍生的SpringBoot等框架,版本小于v5.3.18或v5.2.20
- Spring JavaBean表單參數綁定需要滿足一定條件
- 部署在Tomcat容器中,且日志記錄功能開啟(默認狀態)
環境搭建
新建SpringBoot工程:

添加實體類`User`:

添加`LoginController`:

生成war包并部署到Tomcat容器中啟動。
JavaBean參數綁定分析
JavaBean是一種特殊的類,主要用于傳遞數據信息,這種類中的方法主要用于訪問私有的字段,且方法名符合某種命名規則。如果在兩個模塊之間傳遞信息,可以將信息封裝進JavaBean中。比如在`User`類中再添加一個新類`Test`:

構建如上所示的研究環境,當我們發送如下請求時:

Spring會將參數用`.`進行分割,前面的參數會自動調用`get***`,最后一個參數會自動調用`set***`,依次執行為:
...User->getTest Test->setT...
通過上面這種鏈式的參數解析規則,我們可以`set***`實現修改Spring框架中某些類的屬性。通過深入分析,發現當在`Controller`的參數前加上注解之類,比如`@RequestBody`,將不會進行鏈式解析,這也是限制適用范圍的其中一個關鍵因素。

CVE-2010-1622漏洞補丁分析
Spring歷史上曾經爆出一個漏洞CVE-2010-1622,原理就是基于上面的JavaBean賦值規則。可以通過提交`class.classLoader`參數,最終執行`getClassLoader`函數獲取`ClassLoader`對象,從而可以導致RCE。CVE-2010-1622漏洞補丁位于`CachedIntrospectionResults`:

CVE-2010-1622補丁通過將`classLoader`加入黑名單,導致無法加入解析屬性列表。
JDK 9 Module分析
在JDK9及以上版本的JDK中,`java.lang.Class`類中新增了一個私有變量`module`以及函數`getModule`:

進入`Module`類,發現存在`loader`變量和函數`getClassLoader`:

也就是說,通過`Module`可以獲取Web Context上下文環境的`ClassLoader`對象。
漏洞分析
通過前面的分析,我們在Tomcat+Spring+JDK11環境中構建研究環境,提交`class.module.classLoader`參數同樣可以獲取`ClassLoader`對象,調試發現在對BeanInfo賦值過程中,`CachedIntrospectionResults`中會加載`class org.apache.catalina.loader.ParallelWebappClassLoader`對象,可以繞過黑名單檢查:

`ParallelWebappClassLoader`位于Tomcat環境中,獲取`ParallelWebappClassLoader`對象后,就可以在此基礎上繼續尋找可利用鏈。
可以嘗試采用遞歸方式搜索所有滿足`class.module.classLoader`條件的鏈式調用的屬性,加入一個`help.jsp`文件(腳本參考只考慮int、string與boolean這些基本屬性)。訪問后,可以獲取所有潛在可操控的屬性,結果如下:

一共找到300個符合條件的屬性可以操控(實際應該更多)。其中的`class.module.classLoader.resources.context.parent.pipeline.first.*`對應于`AccessLogValve`類,主要存放Tomcat日志操作的相關屬性:
...class.module.classLoader.resources.context.parent.pipeline.first.directory //日志保存目錄默認為logsclass.module.classLoader.resources.context.parent.pipeline.first.prefix //日志文件名前綴默認為localhost_access_logclass.module.classLoader.resources.context.parent.pipeline.first.suffix //日志文件名后綴默認為.txtclass.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat //日志文件名日期格式默認為.yyyy-mm-dd...
鏈式調用過程如下:
getClass()->LoginControllergetModule()->ModulegetClassLoader()->ParallelWebappClassLoadergetResources()->StandardRootgetContext()->StandardContextgetParent()->StandardEnginegetPipeline()->PipelinegetFirst()->AccessLogValve...

默認情況下日志目錄為`logs`,文件名稱為`localhost_access_log.yyy-mm-dd.txt`,可以通過構造請求修改日志存儲路徑、日志文件名稱、后綴名屬性值。


通過修改日志后綴名、文件名稱、存放位置等屬性,可以寫入jsp的webshell。需要注意的是,由于Tomcat稍微新一點的版本出于安全性考慮,無法直接在URL中攜帶`<`、`{`等特殊字符(否則返回400),我們可以通過`class.module.classLoader.resources.context.parent.pipeline.first.pattern`屬性來修改日志記錄的格式,從而變向寫入webshell。
參考`AbstractAccessLogValve`類使用說明:

比如,可以將敏感字符放到HTTP請求頭中(格式:`%{xxx}i`)。最終實現在`webapps`目錄下寫入webshell,名稱為`localhost_access_log123.jsp`:

修復方式
在官方沒有發布新版本前,可以通過在WAF中加入對`Class.*`等惡意字符串的過濾,或者在Spring應用程序中新建一個全局類實現對惡意字符串的過濾,同時保證這個類被Spring加載到(推薦在Controller所在的包中添加)。完成類添加后,需對項目進行重新編譯打包,并重新發布項目:
import org.springframework.core.annotation.Order;import org.springframework.web.bind.WebDataBinder;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.InitBinder;
@ControllerAdvice@Order(10000)public class a{ @InitBinder public void setAllowedFields(WebDataBinder dataBinder) { String[] abd = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"}; dataBinder.setDisallowedFields(abd); }}
3月31日,官方在`v5.3.18`和`v5.2.20`版本中修復了漏洞,我們看下關鍵補丁:

相比CVE-2010-1622漏洞補丁,新補丁對類型限制更為嚴格,只允許所有類中的`name`屬性合法通過。
小結
漏洞通過`class.module.classLoader`在Web Context上下文環境中找到合適的類屬性進行控制從而實現Getshell,理論上漏洞利用鏈不局限于Tomcat,類似Weblogic、Jetty等其他的Java中間件是否可以利用不好下結論,可以嘗試利用`help.jsp`來尋找潛在的利用點,這方面感興趣的小伙伴可以關注公眾號后與本人深入交流。