Java反序列化学习——CC1链构造
一、危险函数查找
0x01. Transformer类型
CC链的构造几乎大部分都依赖于实现Transformer
接口的类,注释也标注所有该类的说明,并且所有实现该接口的类都可序列化

其中最为明显的是注释中的Invoker
类,全称也就是InvokerTransformer
,可以调用传入的对象的指定方法并返回其调用结果,跟进查看发现该类中的transform
方法提供了动态调用方法的函数,如果能控制变量iMethodName
,iParamTypes
,iArgs
变量就能执行任意对象的任意方法,即我们可以传入一个Runtime
对象,让它调用exec
方法即可执行系统命令。
如果无法控制类属性,则可通过反射去修改(JDK17以下编译)
另外该类存在一个构造函数,说明上面说的变量都是可控的
0x02. 简单攻击链构造
反射动态调用函数的简单例子如下:
那我们可以先简单构造一个使用InvokerTransformer
去执行命令的链,运行以下代码成功弹出计算器。
1 | Runtime r = Runtime.getRuntime(); |
二、攻击链构造
接下来看看有哪些类的方法调用了transform
方法,最好是能够在readObject
方法中直接调用的,不过很明显没有,找到TransformedMap
中有一个checkSetValue
方法中调用该方法,且比较简单,不过由于被protected
,无法直接调用。
TransformedMap
类中有一个静态方法decorate
可以直接获得TransformedMap
实例
继续找看谁调用了checkSetValue
,找到类MapEntry
中setValue
方法
按照上面找到的函数调用关系重新修改下我们的代码,运行弹出计算器后说明这条链没问题
1 | // 实例化一个Runtime对象 |
MapEntry
类是Map
的内置类,在遍历Map
的时候会被实例化,我们可以通过上面的代码调试进行跟进:
当我们进入
entrySet
方法时,如果Check通过就可以返回一个EntrySet
类,否则就返回其父类的entrySet
返回
当我们跟进
Check
方法时,进入的是TransformedMap
的方法,而该方法的Check
方法条件是类属性valueTransformer
不为null
,由于我们在实例化该类时,key
传入的是null
,value
传入的是InvokerTransformer
对象,故不为空,整体判断为true
。
故我们会进入
EntrySet
类的构造函数中
最后在遍历的时候会触发
Map
的iterator
方法
最后通过
next
方法返回一个MapEntry
对象,即我们循环中所使用的entry
另外其实也可以通过TransformedMap
对象中的其他方法去调用transform
方法,例如transformValue
或transformKey
,我这边照样选择使用Value去构造攻击链作为示例
其中同类方法中有个公共的方法put
会同时调用transformValue
和transformKey
方法,根据上面的逻辑可分析出,在调用put
方法时,使传入的key
为null
,value
为Runtime
对象即可完成命令执行
下面是示例代码以及运行后的结果,不过这边自己也没有接着往下去分析这条攻击链到底能不能回归到readObject
中,抛砖引玉以下。
1 | // 实例化一个Runtime对象 |
紧接着继续找谁调用了setValue
方法,发现AnnotationInvocationHandler
方法内有个readObject
方法调用了setValue
,如果可以控制memberValue
的值,那么这条链就构造完成。
再看看该类的构造函数,没有public
标识,故需要通过反射去获取该类并进行实例化
memberValue
是在实例化时传入的参数,故可控
0x01. 问题1-setValue返回对象控制
这个时候就有一个疑问了,调用的setValue
方法时,里面的value
值时AnnotationTypeMismatchExceptionProxy
对象,并不是我们想要的Runtime
,这样肯定时无法Runtime
对象里的exec
方法,但细心的师傅们会发现最开始的Transform
的注释中有一个Constant
类,其描述是总是返回一样的对象,我们跟进看看
可以发现该类的transform
方法返回的是你实例化类时传入的对象,即我们可以控制transform
方法返回的是任意的对象,我们再梳理一下之前构造的攻击链:MapEntry.setValue() -> TransformedMap.checkSetValue() -> InvokerTransformer.transform()
,如果我们把最末端的链对象InvokerTransformer
换成ConstantTransformer
对象,那么不管什么对象setValue
,最后都只返回初始化时传入的对象。
正好该CC依赖还提供了ChainedTransformer
类,调用transform
方法时,可实现链式调用的功能,即本次调用transform
方法得到的对象作为下一次的transform
方法的输入,这样我们就可以解决setValue
传入的参数不可控的问题
接下来我们按照上述想法去构造攻击链
1 | // 使用ChainedTransformer构造一个动态调用链 |
调用
setValue
传入的对象最后会作为ChainedTransformer
对象内的transform
方法的输入对象,而Transform
链第一条只会返回ConstantTransformer
实例化时传入的对象,故可解决该问题
0x02. 问题2-Runtime无法实例化
运行上述代码发现报错了
原来是Runtime
对象的构造方法是私有属性,无法通过newInstance
实例化,故也只能通过反射进行动态调用exec
方法,依旧在ChainedTransformer
类中进行构造,代码如下
1 | // 使用ChainedTransformer构造一个动态调用链 |
运行上述代码即可成功执行系统命令
在
ysoserial
中的攻击链没有采用TransformedMap
类,而是使用LazyMap
,但是思路是一样的