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;

上面我注釋的兩個地方便是關鍵。

    1. multipart/form-data

網上流傳的POC中有這么一部分:

#nike='multipart/form-data'

就是使content_type.contains("multipart/form-data")判斷為true。 當然,完全可以在其他地方添加multipart/form-data這個字符串。

    1. 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. 調試分析

接下來就看看調試關鍵地方了,能夠更進一步了解原理。通過上面補丁對比,以及對流程的掌握。在JakartaMultiPartRequestparsebuildErrorMessage方法下斷點:

可以看到,OGNL語句注入進去了。執行完上面的語句,就彈出計算器了。整個過程,有興趣可以走一下。

0x04 總結

漏洞的原理就是:Struts2默認解析上傳文件的Content-Type頭,存在問題。在解析錯誤的情況下,會執行錯誤信息中的OGNL代碼。

以上是個人分析,期待官方解析~ 研究原理很有趣~


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