Java反序列化Commons-Collections篇07-总结反思篇
0x00 前言
慢慢写,希望能写出一点深度。
0x01 总述
cc1-7链子的总览图:
不用刻意去记链子的组成部分,学明白蕴含的思想就可以了。
- 后半链子
链子底部有两种执行危险方法的方式:反射调用、代码块执行。基于反射调用,我们需要使用InvokeTransformer类;基于代码块执行,我们需要使用InstantiateTransformer等类(在下文会探讨具体的实现过程)。
无论是哪种方法,它们的触发方式都是transform方法。不难想到,即使给了transform方法,怎么保证对应的类符合要求呢?在此基础上,使用ChainedTransformer类和ConstantTransform类去控制条件即可。
现在链子的后半部分已经确定了,只需要一个transform方法触发即可。正是这种相当独立的构造,使得cc链变得很灵活。但这毕竟是基于ChainedTransformer类,如果这个类不能使用了呢?cc2链解决了这个问题:ChainedTransformer类的作用是控制条件,详细地说,其作用是保证InvokeTransformer.transform方法接受的参数是预期的参数。所以如果要替换ChainedTransformer类,必须保证在链子的某一个部分实现控制预期参数这一功能。cc2链把预期参数传入优先队列正是一种解决之道。不妨想想,能不能把cc2链脱离InvokeTransformer类呢?后文会谈谈。
- 前半链子
前半链子首先需要考虑怎么接入transform方法。LazyMap.get方法里调用了factory.transfrom方法,而get方法本身就是非常常用的方法(就同名调用而言)。正是get方法的这一特点,为链子的向前延展打下坚实基础。除了CC1国内链、CC2和CC4,几乎所有链子都链入了LazyMap类。
至于怎么调用get方法,那可真是五花八门,但都有迹可循,非基础扎实之人不能想到。不信?右键get方法查找用法看看。面对繁多的方法调用应该如何寻找突破口,下文也会讲讲个人看法。
0x02 难点
代码块执行
主要讲静态代码块执行。‘
ClassLoader.loadClass()方法不会进行类的初始化,如果接着使用newInstance方法则会进行初始化,正好TemplatesImpl类的newTransformer方法满足条件。用TrAXFilter.TrAXFilter方法连接以后,难点在于怎么触发TrAXFilter构造方法。其实也不难,只需要转换一下思路:直接调用TrAXFilter构造方法的程序几乎是不存在的,但是该方法毕竟是构造方法,寻找动态调用构造方法的方法即可。下面是InstantiateTransformer.transform方法:
接受参数(Class类型)并实例化,既然是transform方法,又可以接入到ChainedTransformer类。
从集成化的角度来说,无论是InvokeTransformer还是InstantiateTransformer对应的方式都是很独立的,最后都能很好地接入到ChainedTransformer里,这为链子的变换构造打下了坚实基础。
get方法前溯
只想到LazyMap类的get方法,当然是没有思路的。不妨看看LazyMap类签名:
1 | public class LazyMap |
只要是调用Map接口get方法的地方,都可以尝试LazyMap.get方法。比如cc1国外链的AnnotationInvocationHandler类的memberValues的类型是Map<String, Object>,可以赋值memberValues为LazyMap类实例,最后调用memberValues的get方法即可。
综上所述,今后对某些调用广泛的方法寻找前调的时候,不妨从功能上进行思考——这离不开对方法接口功能的思考。
CC1-LazyMap链前半部分
该链前半部分连用两个AnnotationInvocationHandler类,但是各自发挥的功能并不相同,很容易绕晕人。
遵循从后往前的寻找规律,第一个AnnotationInvocationHandler类里我们利用的是invoke方法的这块代码:
再往前走,我们需要寻找触发invoke方法的地方。invoke方法可用于动态代理,所以封装一个动态代理:
只需要设法触发动态代理即可。正好AnnotationInvocationHandler类的readObject方法可以触发代理:
所以里面一层的AnnotationInvocationHandler类用invoke方法来触发LazyMap.get方法,外面一层的AnnotationInvocationHandler类用来触发里面一层的AnnotationInvocationHandler类的invoke方法。
cc4/cc2的前提
因为 CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 不再继承 Serializable,导致无法序列化。
同时 CommonsCollections 4的版本 TransformingComparator 继承了 Serializable接口,而CommonsCollections 3里是没有的。这个就提供了一个攻击的路径jdk:jdk8u65
CC:Commons-Collections 4.0
这段引用的信息量还是比较足的。
- CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 不再继承 Serializable,所以cc2链必须是 4.0 版本,高或者低都不行,因为cc2链使用了InvokerTransformer类
- cc4没有采用InvokerTransformer类,所以4.0以上的版本都可以进行尝试。
0x03 要点
性质
每一个类、方法乃至属性都有对应的性质,如果不仔细审查,只怕是空忙活。
- 访问修饰符
访问修饰符决定了我们思考方法调用的方向。
修饰符 | 当前类 | 同包 | 子类(夸包) | 其他包 |
---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
protect | ✅ | ✅ | ✅ | ❌ |
default | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
- 继承与实现
既要关注当前类的父类,也要注意当前类是否继承Serializable接口。
有时会发现当前类没有实现某个方法,不妨想想其父类是否实现该方法。
如果当前类没有继承Serializable接口——无法序列化,就得想办法动态存储当前类。很巧合的是,上文所讲的TrAXFilter类没有继承Serializable接口,没法直接链入链子中,所以InstantiateTransformer类还可以动态存储TrAXFilter类。我不禁思考,InstantiateTransformer类的作用到底是哪一种呢,是动态调用TrAXFilter构造方法,还是存储TrAXFilter类呢?实际上二者兼有。
功能
功能,或者说,基础。这方面需要长时间的积累和思考,比如cc1国内链前半段链子的思路:
这里我们从方法作用入手:
setValue() 的作用:在 Map 中对一组 entry(键值对)进行 setValue() 操作。
所以我们只需要对Map里的entry进行遍历,对每一个entry都采用setValue()即可。
当思路陷入僵局,或者说被调用得太多了,不妨从功能上思考。如果被调用得太少了,不妨从父类、接口上思考。
特点
每一条链子都有自己的特点,这里着重讲一下cc1、cc2、cc3、cc6。
cc1是入门链,用的也不多。
cc2舍弃了ChainedTransformer类,是一个值得思考的方向。但是3.1版本无法构造CC2利用链,原因是3.1版本中的TransformingComparator类没有实现Serializable接口,无法构造利用链。
cc3舍弃了InvokeTransformer类转向InstantiateTransformer类,但是仍然没有抛弃ChainedTransformer类。不妨设想一下,能不能不用ChainedTransformer呢?下文会进行讲解。
cc1和cc3受到jdk版本的限制,但是cc6不受影响,正是因为TransformedMap类进行了修补:
CC1在jdk的包更新到8u71以后,就对漏洞点进行了修复(
CC1TransformedMap
链去掉了Map.Entry
的setValue
方法)(CC1LazyMap
使用了put
对类进行传参),因为反序列化不仅对类有依赖,还对外部的CC库,以及jdk版本有依赖,所以这里CC1就并不那么兼容,就引入了CC6
不受jdk
版本的限制:CC6中引用了
HashMap
来进行反序列化链子的构造,通过HashMap
里面的readObject
方法来调用里面的put
方法,然后调用hash方法最后调用hashCode
方法,如果从hashCode
里面能找到的一个调用get
的链,就成功了,这样就不会受到jdk版本的限制
cc3真的受jdk版本影响吗?用cc1国外链的前半段(LazyMap类)就可以了。
不难看出,用哪条链都无所谓,重要的是记住每条链的特点,根据不同的情况进行拆分重构。谈到这里,就必须谈谈cc链各部分的独立性了。
独立
前文多次谈到独立,最常见的就是cc链后半链的独立性。前文抛出了一个问题:ChainedTransformer类能不能被随意替换?
独立性要求入口一致,结果/功能一致。观察cc2链,不难发现TransformingComparator.compare方法与ChainedTransformer.transfrom方法的结果/功能一致,但是入口不一致,甚至是不能向上兼容(目前没找到向上兼容的方法),所以当禁用了ChainedTransformer类后,我们只能遵循cc2链(前半部分)。
当InvokeTransformer类被禁用,可以使用InstantiateTransformer类,也就是执行静态代码块,但是这往往要求先上传危险类;在此基础上,如果ChainedTransformer类被禁用,那就只能采用TransformingComparator类,也就是cc4链前半段。
0x04 小结
该说的都说的差不多了,是时候做几道题了。