复现环境
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
方法后恶意类会被加载然后被实例化,最终执行命令。
完整的利用链如下图所示