JAVA反序列化CC链1补充
JAVA反序列化CC链1补充
前言
在前面,我们已经彻底分析了CC链1的执行流程,至于这个补充,是因为我们讲的那个CC1是改良版本的,真实的版本和我们讲的略有差异,所以我们再次进行分析,此次分析,需要我们掌握JAVA的动态代理,没准备的师傅可自行了解或参考我的文章,闲言少叙,我们开始
书接上回
既然是基于CC1链的再分析,那肯定有些东西是不会变的,那就是最终执行命令的逻辑
既然标注的地方不变,那要执行命令,就是找谁能执行ChainedTransformer里的transform函数,那我们直接去找使用过该函数的地方
我们能看到我们以前使用的TransformedMap类,毕竟找的方法都是一致的,这次,我们使用LazyMap类,LazyMap中的get方法可以执行transform函数,只要factory可控就行
1 | public static Map decorate(Map map, Transformer factory) { |
我们找到其decorate函数并发现参数是可控的,而且传入类型是符合的,我们完全可以传入我们构造好的ChainedTransformer,接下来就是继续去寻找哪里调用了get方法,最好能是某个可控类中的readObject方法调用的
可惜,我们未能如愿,但是在AnnotationInvocationHandler类中找到了调用get方法的地方
1 | AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { |
我们继续看,本来我们应该接着去找哪里调用了invoke方法,可是这个方法,越想越奇怪,我们赶忙去看看这个类继承于哪个接口
1 | public interface InvocationHandler { |
这个类竟然继承于InvocationHandler接口,这是一个动态代理的接口,所以我们无需去找谁调用了invoke方法,只要我们声明一个代理类,将AnnotationInvocationHandler类作为实例化代理类的第三个参数,然后我们不管调用该代理类的任何方法,我们都能跳到AnnotationInvocationHandler中的invoke方法
说了半天,我们该如何实现呢,首先,我们还是得找到链子的开端,不然呢,我们不知道去代理哪个类,这其实很难找的,这里作者选用的链子的起始类依然是AnnotationInvocationHandler类,与前面的CC1的链子开端是一样的,那我们继续想,这个AnnotationInvocationHandler类在构造时只有两个参数,第一个是注解类,第二个是一个Map类,注解类在其readObject方法里面几乎是无用的,那我们只能寄希望于Map类,那这个Map,我们到底该如何传呢,结合我们前面的分析,没错,传入一个Map的代理类
payload:
1 | Transformer[] transformers = new Transformer[]{ |
最前面的没啥好说的,LazyMap的构造也没啥好说的,前面已经说过了其参数可控的特性,所以我们传入chainedtransformer
前面说过了,作者依然选用AnnotationInvocationHandler作为链子的开端,这里我们第二个参数传入的是一个代理的Map类,传入Map类是因为该类接受的第二个参数就是Map类型,至于为啥一定要传入的是代理Map类,我们前面笼统的说过了,这里我们展开说说它的详细调用过程
首先o2的反序列化被读入到AnnotationInvocationHandler类中的readObject方法中
当执行到红框所指的位置时,由于memberValues是我们传入的代理Map类,那么,根据代理的特性,会执行Map代理类中InvocationHandler类里面的invoke方法,由于这个代理Map类,我们传入的InvocationHandler其实就是o1,所以,此时就会执行o1中的invoke方法
那么o1是啥呢,其实o1还是AnnotationInvocationHandler类,所以我们跟进invoke方法
发现要经过两个判断,但此时都轻松通过,因为method.getName()获取到的是entrySet[动态代理的知识],所以说判断轻松通过,memberValues.get(member)这里,因为执行的是o1中的invoke方法,所以memberValues就是lazyMap[因为我们在实例化o1时传入的就是lazyMap],到这里我们就发现,此时将执行lazyMap.get(member),就会走到我们之前找到的地方
此时只要通过这个if判断,我们就能执行factory.transform(key)了,而factory我们早已经控制为chainedtransformer
此处的map是我们先前传入的hashMap,里面是空的,所以也轻松通过,成功RCE
这就是一定要传入代理类的原因,传入代理类,才能跳到lazyMap的get方法之中
讨论
但是,要说的一定是,这条CC链1无论是我们之前分析的改版,还是现在分析的原版,都是有JDK的版本限制的,就是8u65之前,8u65之后,随着AnnotationInvocationHandler类中的readObject方法的改写,使这条链永远的成为了过去式
参考资料
【Java反序列化CommonsCollections篇(二)-最好用的CC链】https://www.bilibili.com/video/BV1yP4y1p7N7?vd_source=c210ec42fbb60565fb74b1baf7a2c3ef