Author:angelwhu
date:2017/03/07
0x00 漏洞公告
請看https://cwiki.apache.org/confluence/display/WW/S2-045
這個漏洞應該后續會有官方詳細分析。這里談談個人的理解,也分享下重現漏洞的思路。
首先,仔細閱讀漏洞描述:
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.
描述中明確了兩點:
- 通過
Content-Type這個header頭,注入OGNL語言,進而執行命令。 - 漏洞的點在于,由于Strus2對錯誤消息處理時,出現了紕漏。
0x01 關于Struts2上傳機制
部分網上描述為:基于 Jakarta plugin插件。
這種描述是不對的,Struts2有其插件機制,如之前爆過S2-037漏洞的REST插件。但Struts2上傳默認使用的是org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest類,對上傳數據進行解析。不存在插件這個說法,只不過它最終調用了第三方組件common upload完成上傳操作。
注:以下Struts2源碼版本均是2.3.20。
具體可以看看源碼流程,首先進入StrutsPrepareAndExecuteFilter類,這是Struts2默認配置的入口過濾器。在里面可以看到,Struts2首先對輸入請求對象request的進行封裝:
request = prepare.wrapRequest(request);
跟進這條語句,可以看到封裝為StrutsRequestWrapper的過程:
if (request instanceof StrutsRequestWrapper) {
return request;
}
String content_type = request.getContentType();
if (content_type != null && content_type.contains("multipart/form-data")) {//判斷是不是post表單
MultiPartRequest mpr = getMultiPartRequest();//默認返回JakartaMultiPartRequest類
LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider);
} else {
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
}
return request;
上面我注釋的兩個地方便是關鍵。
-
multipart/form-data
網上流傳的POC中有這么一部分:
#nike='multipart/form-data'
就是使content_type.contains("multipart/form-data")判斷為true。 當然,完全可以在其他地方添加multipart/form-data這個字符串。
-
- getMultiPartRequest()
這個方法可以繼續追蹤下去。通過配置struts.multipart.parser屬性,可以指定不同的解析類,而默認的就是上面說的org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest類。
網上可以查閱得到這樣的解釋:
struts.multipart.parser:該屬性指定處理multipart/form-data的MIME類型(文件上傳)請求的框架,該屬性支持cos、pell和jakarta等屬性值,即分別對應使用cos的文件上傳框架、pell上傳及common-fileupload文件上傳框架。該屬性的默認值為jakarta。
更進一步的官方說明:https://cwiki.apache.org/confluence/display/WW/File+Upload#FileUpload-AlternateLibraries
0x02 漏洞補丁對比
漏洞分析必然要補丁對比了。查看struts2在git上的commit,發現描述為Uses default error key if specified key doesn't exist的修改:
2.5.10.1版本修改:
https://github.com/apache/struts/commit/b06dd50af2a3319dd896bf5c2f4972d2b772cf2b

2.3.32版本修改:
https://github.com/apache/struts/commit/352306493971e7d5a756d61780d57a76eb1f519a

可以清晰的看到,都去掉了這樣的一個方法:
LocalizedTextUtil.findText(......);
然后,就得到了第三個關鍵:
- sink點
后面通過動態調試追蹤可以發現:就是通過這個方法LocalizedTextUtil.findText,最終到達執行命令的地方。這里暫時可以看做是一個sink點。
當payload進入這里后,就可以通過OGNL執行命令了。同時,直觀感覺功能是在處理error消息。
0x03 漏洞重現及調試分析
1. 簡單重現
環境配置:
- tomcat7
- struts2.3.20
這里說一下,通過上面的原理分析。可以猜到,并不需要找個上傳的地方。只需要模擬上傳發包即可,危害巨大啊......
所以,我使用Struts2.3.20版本的struts2-blankwar包,直接測試漏洞:

我用的POC,是之前版本的。單純測試并驗證我的想法:
Content-Type: haha~multipart/form-data %{#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,@java.lang.Runtime@getRuntime().exec('calc')};
得到的結論是:
直接在Content-Type注入OGNL語句,即可執行命令。當然,包含multipart/form-data字符串。
2. 調試分析
接下來就看看調試關鍵地方了,能夠更進一步了解原理。通過上面補丁對比,以及對流程的掌握。在JakartaMultiPartRequest的parse和buildErrorMessage方法下斷點:

可以看到,OGNL語句注入進去了。執行完上面的語句,就彈出計算器了。整個過程,有興趣可以走一下。
0x04 總結
漏洞的原理就是:Struts2默認解析上傳文件的Content-Type頭,存在問題。在解析錯誤的情況下,會執行錯誤信息中的OGNL代碼。
以上是個人分析,期待官方解析~ 研究原理很有趣~
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/241/