复现环境
1 | Mac OS Big Sur |
Ysoserial环境和Web Server环境配置和这里的配置是一样的。
CC2复现
Payload生成

Payload测试

复现成功
前置知识
Javassist
Javassist是用来处理java字节码的类库, java字节码一般存放在后缀名称为class的二进制文件中。每个二进制文件都包含一个java类或者是java接口。对于Javassist有很多知识点,这里我们仅仅说几个payload中用到的一些用法。
1 | ClassPool:一个基于哈希表实现的CtClass对象容器,其中键名是类名称,值是表示该类的CtClass对象 |
举一个例子
1 | package javatest; |
上述代码会在指定文件下生成一个新的字节码文件。我们发现,动态生成的类在原有类的基础上添加了静态代码块,如果我们加载这个类的话,静态代码块中的内容就会被执行。

现在我们通过toClass拿到生成的类,并通过newInstance获取实例对象
1 | package javatest; |
成功弹出计算器

PriorityQueue
PriorityQueue实际上是一个堆(不指定Comparator时默认为最小堆),队列既可以根据元素的自然顺序来排序,也可以根据 Comparator来设置排序规则。队列的头是按指定排序方式的最小元素。如果多个元素都是最小值,则头是其中一个元素。新建对象的时候可以指定一个初始容量,其容量会自动增加。
1 | PriorityQueue优先级队列的函数定义 |
Ysoserial Payload生成分析
createTemplatesImpl
在生成CC2 Payload的时候,首先执行的是Gadgets.createTemplatesImpl(command)

在createTemplatesImpl方法中首先会通过getProperty方法获取系统属性properXalan的值,properXalan的默认属性是false,通过调试发现走的是下面的return条件。

跟进发现,首先获取了TemplatesImpl的实例对象。之后通过Javassist动态修改StubTransletPayload类。

接着通过get方法获取abstTranslet类,并通过setSuperclass将该类作为新建类的父类,因为在前面我们获取到了StubTransletPayload类,而StubTransletPayload是继承abstTranslet类的


通过上述Javassist操作,就可以将我们写的恶意代码注入到新的字节码文件中中的静态代码块部分
之后通过CtClass.toBytecode方法获取到恶意类的字节码,并通过java反射机制将字节码填充到TemplatesImpl实例对象的_bytecodes数组中。

之后填充了_name和_tfactory字段,并将TemplatesImpl实例对象返回

InvokerTransformer
返回TemplatesImpl实例对象之后,接着获取了InvokerTransformer实例对象,并做了一些初始化。

PriorityQueue
接着创建一个优先级队列,指定队列的初始容量和比较器。然后填充了两个1来占位初始化。

在优先级队列里,通过TransformingComparator(transformer)获取构造器实例对象,此时的transformer就是上面InvokerTransformer类型的transformer实例对象。

接着优先级队列每次比较的时候,都会调用比较器的compare方法,此时服务端就会调用TransformingComparator.compare方法,进而执行this.transformer.transform方法,这个时候就会执行InvokerTransformer.transform方法

setFieldValue
继续回到CC2 Payload文件中,setFieldValue方法通过反射将iMethodName修改为newTransformer

getFieldValue
接下来获取到优先级队列的实例对象,并修改了其字段值,将我们构造的恶意类注入

修改之后,优先级队列会调用比较器的compare方法去比较templates和1的值,此时会调用执行InvokerTransformer.transform(templates)方法。此时由于templates是TemplatesImpl类型的,因此实际最后会执行TemplatesImpl.newTransformer方法。
最后Ysoserial返回的优先级队列实例对象如下

CC2 Payload利用原理
通过Ysoserial分析可以看到Payload返回的是一个PriorityQueue对象。在对Web Server进行调试的时候,我们直接将断点打在PriorityQueue的readObject方法处。
PriorityQueue路径为rt.jar.java.util.PriorityQueue
PriorityQueue.readObject

在readObject方法中,首先通过defaultReadObject方法反序列化数据流。之后通过readInt方法读取优先级队列的长度,接着循环读取数组queue中的内容

PriorityQueue.heapify
之后调用heapify方法,将无序数组还原成优先级队列。在该方法中会循环找最后一个非叶子结点,然后倒序调用siftDown方法

PriorityQueue.siftDown
在siftDown方法中,会调用siftDownUsingComparator方法

PriorityQueue.siftDownUsingComparator
该方法主要用于形成最小堆

cc4.TransformingComparator
该方法会获取需要比较的变量。从而执行this.transformer.transform,因为我们已经将this.transformer指向了InvokerTransformer实例对象。因此会执行InvokerTransformer.transform方法,而transform中的参数就是我们传入的恶意类TemplatesImpl

InvokerTransformer.transform
在Ysoserial分析payload生成的时候我们将iMethodName的值改为了newTransformer,因此这里会反射调用TemplatesImpl.newTransformer方法

TemplatesImpl.newTransformer
TemplatesImpl路径为rt.jar.com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
在构建TemplatesImpl实例对象时,会调用getTransletInstance方法。

TemplatesImpl.getTransletInstance
在getTransletInstance方法中,首先会判断_name是否为空,如果为空,直接return。接着会判断_class并进入defineTransletClasses方法中

TemplatesImpl.defineTransletClasses
该方法会对_bytecodes字段进行解析,最后将_transletIndex的值赋为0

newInstance
此时的_class[_transletIndex]即为_class[0]也就是我们通过啊Ysoserial构造的恶意类,调用newInstance方法后恶意类会被加载然后被实例化,最终执行命令。

完整的利用链如下图所示
