0x00 前言

慢慢写,希望能写出一点深度。

0x01 总述

cc1-7链子的总览图:

image-20250106203423837

不用刻意去记链子的组成部分,学明白蕴含的思想就可以了。

  • 后半链子

链子底部有两种执行危险方法的方式:反射调用、代码块执行。基于反射调用,我们需要使用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方法:

image-20241225203206553

接受参数(Class类型)并实例化,既然是transform方法,又可以接入到ChainedTransformer类。

从集成化的角度来说,无论是InvokeTransformer还是InstantiateTransformer对应的方式都是很独立的,最后都能很好地接入到ChainedTransformer里,这为链子的变换构造打下了坚实基础。

get方法前溯

只想到LazyMap类的get方法,当然是没有思路的。不妨看看LazyMap类签名:

1
2
3
public class LazyMap
extends AbstractMapDecorator
implements Map, Serializable {···}

只要是调用Map接口get方法的地方,都可以尝试LazyMap.get方法。比如cc1国外链的AnnotationInvocationHandler类的memberValues的类型是Map<String, Object>,可以赋值memberValues为LazyMap类实例,最后调用memberValues的get方法即可。

综上所述,今后对某些调用广泛的方法寻找前调的时候,不妨从功能上进行思考——这离不开对方法接口功能的思考。

CC1-LazyMap链前半部分

该链前半部分连用两个AnnotationInvocationHandler类,但是各自发挥的功能并不相同,很容易绕晕人。

遵循从后往前的寻找规律,第一个AnnotationInvocationHandler类里我们利用的是invoke方法的这块代码:

image-20241226144523613

再往前走,我们需要寻找触发invoke方法的地方。invoke方法可用于动态代理,所以封装一个动态代理:

image-20241226144818995

只需要设法触发动态代理即可。正好AnnotationInvocationHandler类的readObject方法可以触发代理:

image-20241226145446791

所以里面一层的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

这段引用的信息量还是比较足的。

  1. CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 不再继承 Serializable,所以cc2链必须是 4.0 版本,高或者低都不行,因为cc2链使用了InvokerTransformer类
  2. 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.EntrysetValue方法)(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 小结

该说的都说的差不多了,是时候做几道题了。