序言
前面分析了CC1 链,其是有版本限制的,在JDK 1.8 8u71版本以后,对AnnotationInvocationHandler的readObject进行了改写,导致高版本JDK无法使用CC1。
CC2利用的是cc4.0,因为cc3.2-3.1中TransformingComparator没有继承Serializable,所以无法进行java反序列化。


CC2链 并不是使用 AnnotationInvocationHandler构造,而是使用 javassist 和 PriorityQueue 构造链子。
Maven - pom.xml
1 | <dependency> |
POC链:

前置知识
javassist
通常我们需要将.java编译成.class才能正常运行,命令行通过javac进行编译,利用编译生成的固定格式的字节码(.class)来供jvm虚拟机进行使用,每一个class文件中都包含这一个java类和一个接口。
javalist就是一个处理字节码的类库,能够动态的修改class中的字节码。
常用用法:
1.ClassPool是一个CtClass对象的容器,一个CtClass必须从中进行获取,通过ClassPool.getDefault()返回了默认的类池。
ClassPool pool=ClassPool.getDefault();
2.创建名为Evil的类
CtClass test=pool.make('Evil');
3.添加类搜索路径,通过 ClassPool.getDefault(); 获取的ClassPool使用JVM的类进行搜索路径,但是如果程序运行在JBOSS或Tomcat就有可能回找不到用户的类,因为Web服务器回使用多个类加载器作为系统加载器,这时候我们必须要要通过如下命令来添加我们的路径:
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
4.设置要继承的类:
test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
5.创建一个空的类初始化器(静态构造函数)
CtConstructor constructor = test.makeClassInitializer();
6.将字节码插入到开头:
constructor.insertBefore("System.out.println(\"Hello,Teabo\");");
7.再将编译的类创建为.class文件:
test.writeFile('./');
Demo:


ClassLoader#defineClass
ClassLoader是java的类加载器,负责将字节码转化成内存中的Java类,加载过程中采用双亲委派来实现加载。
这里可以利用类加载器的defineClass方法来加载我们的字节码

用反射方法调用 defineClass方法
需要注意的是,ClassLoader#defineClass 返回的类并不会初始化,只有这个对象显示调用其构造函数时才会执行初始化代码,所以要找办法调用返回得类的构造函数才能执行命令,这时候TemplatesImpl出场,为什么选择它,因为它最后会自动实例初始化,看到下面分析就知道了。
TemplatesImpl
在TemplatesImpl类中定义了一个内部类

这个类对defineClass进行了重写,并且没有显式的对定义域声明,因此可以被外部进行调用。
这个调用的内部过程为:
1 | TemplatesImpl.getOutputProperties() |
1.TemplatesImpl.getOutputProperties()

2.TemplatesImpl.newTransformer()

3.TemplatesImpl.getTransletInstance()

4.TemplatesImpl.defineTransletClasses()

getSuperclass()将字节码还原成class对象

TemplatesImpl内部链就是这样 蓝色框为需满足的条件。
注意还要设置父类

因为有父类判断,通过才赋值下标

现在去主函数里利用TemplatesImpl代替 ClassLoader 来加载字节码

成功加载字节码

PriorityQueue
构造方法:
PriorityQueue() 创建一个PriorityQueue,根据其自然顺序对元素排序
PriorityQueue(int ) 创建指定容量的PriorityQueue
常用方法:

前置知识 javassist生成修改字节码 ClassLoader TemplatesImpl加载字节码 大致就是这样。
CC2利用链分析
完整利用链
1 | ObjectInputStream.readObject() |
(偷张图~)

TransformingComparator
在CC2中是利用 TransformingComparator的compare来触发 transform的方法(感觉各个版本的CC链就是花式想办法触发transform方法)

如果传入的obj1可控,那么就可以利用 InvokerTransformer 反射执行任意类的任意方法。
InvokerTransformer在CC1中已经详细分析过了
这里如果compare中调用InvokerTransformer,并且传入的obj1包含恶意的任意类任意方法,那么就能反射执行命令。



已知TransformingComparator.compare()可以触发transform,那么现在就要找什么地方调用这个compare(),并且要参数可控。在CC2中用的是 PriorityQueue 队列来触发
PriorityQueue

从readObject()开始看, 先是对传入的ObjectputStream进行反序列化然后788行赋值给queue数组。
那么我们传入的肯定就是序列化数据了。
所以来看writeObject()

writeObject序列化时传入的参数是 queue数组,他是这个类的一个属性,既然是属性,我们就可以通过反射方法修改控制它。
回到readObject(),在最后调用了heapif(),进入其


进入siftDown()

进入siftDownUsingComparator()

可以看到有comparator.compare的调用 ,同时参数x可控。
因此就可以控制这里的comparator为TransformingComparator。
所以就成功利用 PriorityQueue 触发到了 compare,和上面半段联系起来。
Demo-1:

先手动给出Transform利用链,然后反射赋值PriorityQueue的compare属性,从而调用到TransformingCompare.transform(),进而触发Transform利用链。
注意这里为什么给出两次add(),因为如果只给出一次,在heapify()中的i是-1,add()就相当于控制size和添加queue[]了。
i=-1时

i=0时

以上是利用ChainedTransform链达到命令执行目的。
接下来的最后一步就简单了,就是利用TemplatesImpl加载包含任意CMD的字节码
1 | javassist生成字节码 -> 利用InvokerTransformer触发TemplatesImpl.newTransformer -> TemplatesImpl加载字节码 -> 任意命令执行 |
最终POC
1.javassist生成字节码

2.生成TemplatesImpl实例

3.反射设置TemplatesImpl链子所需要满足的值

4.准备InvokerTransformer,用于触发TemplatesImpl.newTransformer

5.准备TransformingComparator,用于触发第4步的InvokerTransformer

6.创建PriorityQueue队列,并传入第5步的TransformingComparator,在PriorityQueue.java中触发到TransformingComparator.compare()

这里对queue属性赋值一个数组,为什么不直接传递templates的原因就是queue是一个Object[]数组。并且元素个数为2的原因就是之前解释过为什么两次add()的原因。


那这样和上面的Demo-1有什么区别呢?
上面的Demo1不是加载字节码的方式执行命令,而是通过ChainedTransformer来执行命令,
所以也就不需要去触发TemplatesImpl.newTransformer了,只需要在TransformingComparator.compare()中触发
ChainedTransformer.transform即可,所以才不需要将精心构造的templatesImpl类传入queue数组,只需要控制comparator属性的值为TransformingComparator类即可。
进入到siftDownUsingComparator中触发compare()
跳过第一个if后直接触发到TransformingComparator.compare()

其中 templatesImpl作为参数一 obj1 传递

调用Invokerformer.transform(templates)
结合之前反射赋值的iMethodName="newTransform"
最终利用Invoke反射调用执行templatesImpl.newTransform

再看看触发
templatesImpl.newTransform之后templatesImpl内部发生了什么
1.
2.

3.newInstance() 自动初始化加载的恶意字节码,执行命令


为什么我们的字节码文件只要初始化之后就会运行呢,也没有去调用呀??
是因为生成的字节码类的构造函数是static。所以初始化加载的时候会调用里面的代码。

包含cmd的字节码在这就被加载执行了,链也就结束了
所以说这里完整POC的调用链是这样的:
1 | PriorityQueue.readObject() |
代码:
1 | package CC2; |
CC2的分析就到这里了,前前后后啃了2 3天,可能对一些细节一些概念还不能完全理解,甚至有些地方不正确 :(,以后再回头来看可能会有新的理解。要说CC2和CC1除了调用链这些区别外还有什么我觉得最大的不同,那就是CC1是用ChainedTransformer手动构造一条InvokerTransformer利用链然后去找链触发,而CC2是引入了字节码,通过生成、加载字节码来执行命令,所以不需要像CC1那样构造一个命令执行链,只需要把要执行的命令写入字节码中,然后去加载字节码触发命令执行。