Smi1e@Pentes7eam
漏洞信息:
https://cwiki.Apache.org/confluence/display/WW/S2-045
Struts2默認使用
org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest
類對上傳數據進行解析。其在處理Content-Type
時如果獲得非預期的值的話,將會拋出一個異常,在此異常的處理中會對錯誤信息進行OGNL表達式解析。
漏洞復現
影響范圍
Struts 2.3.5 - 2.3.31, Struts 2.5 - 2.5.10
漏洞分析
Struts 2.3.5 - 2.3.31和Struts 2.5 - 2.5.10漏洞入口不同
前者調用棧
在過濾器org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
的dofilter
方法中下斷點,跟進this.prepare.wrapRequest(request);
,它會封裝request
對象。
其中會判斷 content_type
中是否含有字符串multipart/form-data
,如果有則實例化MultiPartRequestWrApper
對象,并傳入對應參數來對請求進行解析。其中this.getMultiPartRequest;
會去獲取用來處理文件上傳請求的解析類,默認是org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest
。我們可以通過配置struts.multipart.parser
屬性來指定不同的解析類。
跟進 this.multi.parse(request, saveDir);
,這里的this.multi
就是默認的JakartaMultiPartRequest
對象。
這里由于對上傳請求解析時發現錯誤所以會進入到下面的 catch
操作中,跟進this.buildErrorMessage
它會調用LocalizedTextUtil.findText
處理錯誤信息
不斷跟進會發現他會把message傳入TextParseUtil.translateVariables
進行表達式解析。
后面就還是以前的流程了。
而 Struts 2.5 - 2.5.10中org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper.buildErrorMessage
并沒有調用LocalizedTextUtil.findText
它是在攔截器 FileUploadInterceptor
中調用的LocalizedTextUtil.findText(error.getClazz, error.getTextKey,
從而對錯誤信息進行OGNL表達式解析
ActionContext.getContext.getLocale, error.getDefaultMessage, error.getArgs);
在2.3.30/2.5.2之后的版本,我們可以用來bypass黑名單過濾的 _memberAccess
和DefaultMemberAccess
都進入到了黑名單中。
不過個人認為沙盒修復的關鍵是Ognl3.0.18+和Ognl3.1.10+中 OgnlContext
刪除了_memberAccess
這個key,從而禁止了對_memberAccess
的訪問。Struts2.3.30開始使用的是Ognl3.0.19,Struts2.5.2開始使用的是Ognl3.1.10。從S2-045的payload中也能看出來,直接可以執行(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)
,后面才開始查看是否存在#_memberAccess
。
-
%{(#fuck='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='CMD').(#iswin=(@JAVA.lang.System@getProperty('os.name').toLowerCase.contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=newjava.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)}
在初始化了 OgnlValueStack
對象后,會對OgnlValueStack
對象進行依賴注入。
這個時候會調用com.opensymphony.xwork2.ognl.OgnlValueStack$setOgnlUtil
將黑名單添加進來賦值給OgnlValueStack
的securityMemberAccess
對象
這里需要注意的是由于 OgnlUtil
默認為singleton
單例模式,因此全局的OgnlUtil
實例都共享著相同的設置。如果利用OgnlUtil
更改了設置項(excludedClasses、excludedPackageNames、
)則同樣會更改
excludedPackageNamePatterns_memberAccess
中的值。
為什么改了 OgnlUtil
的值以后_memberAccess
的值也會改變呢?
首先上面說過了全局的 OgnlUtil
實例都共享著相同的設置。然后因為SecurityMemberAccess
類的excludedClasses
等屬性都是集合類型
而在java中集合賦值時傳遞的是一個引用,我們對集合做的任何操作都會影響到它賦值過的變量。所以在解析Ognl表達式時,當 OgnlUtil
的excludedClasses
等安全屬性被清空時,_memberAccess
對象的excludedClasses
等安全屬性也直接隨之被清空了。
漏洞修復
不把報錯的信息放到LocalizedTextUtil.findText
方法中去,從而保證報錯的content-type
不會進行OGNL表達式解析。
2.5.10.1版本修改:
https://github.com/apache/struts/commit/b06dd50af2a3319dd896bf5c2f4972d2b772cf2b
2.3.32版本修改:
https://github.com/apache/struts/commit/352306493971e7d5a756d61780d57a76eb1f519a