JavaSec-CC1
# CC1 链
# Commons Collections
Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了 Java 的 Collection (集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类。
作为 Apache 开源项⽬的重要组件,Commons Collections 被⼴泛应⽤于各种 Java 应⽤的开发,⽽正 是因为在⼤量 web 应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性。
Apache Commons Collections 中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java 的反射机制来调用任意函数,叫做 InvokerTransformer。
环境:
- CommonsCollections <= 3.2.1
- java < 8u71
存一下老版本 java 下载地址:
Oracle JDK 8u65 全平台安装包下载 - 码霸霸 (lupf.cn)
导入 Maven 依赖
1 | <dependency> |
# 利用过程分析
利用这些漏洞的方法一般是寻找到某个带有危险方法的类,然后溯源,看看哪个类中的方法有调用危险方法 (有点像套娃,这个类中的某个方法调用了下个类中的某个方法,一步步套下去,这里表述的可能不是特别清晰,不过没事,慢慢看下去),并且继承了序列化接口,然后再依次向上回溯,直到找到一个重写了 readObject 方法的类,并且符合条件,那么这个就是起始类,我们可以利用这个类一步步的调用到危险方法 (这里以 **“Runtime 中的 exec 方法为例”**),这就是大致的 Java 漏洞链流程。
入口: org.apache.commons.collections.Transformer ,transform 方法有 21 种实现

入口类: org.apache.commons.collections.functors.InvokerTransformer ,它的 transform 方法使用了反射来调用 input 的方法,input,iMethodName,iParamTypes,iArgs 都是可控的

首先先尝试直接利用 invoketransformer 来执行命令

去找一个其它的类有 transform 方法,并且传入的 Object 是可控的,然后我们只要把这个 Object 设为 InvokeTransformer 即可,我们全局搜索 transform 方法,能够发现很多类都是有 transform 方法的,我们这里先研究的是 CC1,所以我们直接看 TransformerMap 类

在 TransformedMap 中的 checkSetValue 方法中调用了 transform,valueTransformer 是构造的时候赋的值,再看构造函数
构造函数是一个 protected,所以不能让我们直接实例赋值,只能是类内部构造赋值,找哪里调用了构造函数

一个静态方法,这里我们就能控制参数了

现在调用 transform 方法的问题解决了,返回去看 checkSetValue,可以看到 value 我们暂时不能控制,全局搜索 checkSetValue,看谁调用了它,并且 value 值可受控制,在 AbstractInputCheckedMapDecorator 类中发现,凑巧的是,它刚好是 TransformedMap 的父类


在遍历集合的时候就用过 setValue 和 getValue ,所以只要对 decorate 这个 map 进行遍历 setValue,由于 TransformedMap 继承了 AbstractInputCheckedMapDecorator 类,因此当调用 setValue 时会去父类寻找,写一个 demo 来测试一下:
1 | package com.Fc0; |

继续查找用法,看看有哪些方法里面调用了 setValue 并且可以被我们所利用,最好是直接来个重写过的 readObject 方法,里面调用了 setValue,于是我们在 AnnotationInvocationHandler 这个类中看到有个调用了 setValue 方法的 readObject 方法,很完美的实现了代替之前 Map 遍历功能:
感谢我的朋友 Mak 的指导,才知道要在 rt.jar 里找😭(尴尬.jpg
sun.reflect.annotation.AnnotationInvocationHandler:

我们分析下 AnnotationInvocationHandler 这个类,未用 public 声明,说明只能通过反射调用

查看一下构造方法,传入一个 Class 和 Map,其中 Class 继承了 Annotation,也就是需要传入一个注解类进去,这里选择 Target

现在有个难题是 Runtime 类是不能被序列化的,但是反射来的类是可以被序列化的,还好 InvokeTransformer 有一个绝佳的反射机制,构造一下:
1 | Method RuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class); |
RuntimeMethod:利用InvokerTransformer获取Runtime类的getRuntime方法。这个代码段通过反射获取Runtime类的getRuntime方法,这是一个静态方法,返回Runtime实例。r:调用getRuntime方法,获取Runtime实例。这一步实际调用了getRuntime方法,获得一个Runtime对象,它可以用于执行系统命令invokerTransformer:通过InvokerTransformer,使用exec方法在运行时执行系统命令calc。这行代码调用Runtime的exec方法,传递"calc"参数,企图打开计算器应用程序。
现在还有个小问题,其中我们的 transformedmap 是传入了一个 invokertransformer ,但是现在这个对象没有了,被拆成了多个,就是上述四段代码,得想个办法统合起来,这里就回到最初的 Transformer 接口里去寻找,找到 ChainedTransformer ,刚好这个方法是递归调用数组里的 transform 方法

我们就可以这样构造:
1 | Transformer[] transformers = new Transformer[]{ |
到这一步雏形以及可以构造出来了
1. serialize 和 deserialize 方法:
serialize(Object obj):将传入的对象序列化并写入cc1.bin文件中。deserialize(String filename):从文件cc1.bin中读取序列化的对象。
这两个方法是为了进行序列化与反序列化操作,目的是利用序列化数据触发恶意代码执行。
2. 构造 ChainedTransformer :
1 | Transformer[] transformers = new Transformer[]{ |
这部分代码使用 InvokerTransformer 和 ChainedTransformer 构建了一个链式转换器,按顺序调用:
- 获取
Runtime类的getRuntime方法。 - 调用
getRuntime方法,获取Runtime实例。 - 通过
Runtime.exec("calc")执行系统命令,打开计算器。
3. 使用 TransformedMap 装饰 Map 对象:
1 | Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer); |
这段代码将普通的 HashMap 对象装饰为 TransformedMap ,在调用 put 、 get 等方法时,将会触发链式转换器中的逻辑。
4. 构造 AnnotationInvocationHandler 实例:
1 | Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); |
这里通过反射创建了 AnnotationInvocationHandler 的实例,并传入了目标类和经过装饰的 Map 。当该对象被序列化或反序列化时,恶意代码将被执行。
5. 序列化与反序列化: 最后,将构造出的对象序列化到文件中,并在反序列化时,通过 chainedTransformer 的执行链,最终执行 Runtime.exec("calc") 。
但是这里反序列化并不能执行命令,why?原因在于 AnnotationInvocationHandler 里触发 setValue 是有条件的,我们调试追踪进去看看:

要想触发 setValue 得先过两个 if 判断,先看第一个 if 判断,memberType 不能为 null,memberType 其实就是我们之前传入的注解类 Target 的一个属性,这个属性哪里来的?就是我们最先传入的 map map.put("1","2")
获取这个 name:1,获取 1 这个属性,很明显我们的 Target 注解类是没有 1 这个属性的,我们看一下 Target 类

Target 是有 value 这个属性的,所以我们改一下 map, map.put("value", 1) ,这样就过了第一个 if,接着往下看第二个 if,这里 value 只要有值就过了,成功到达 setValue,但这里还有最后一个问题,如何让他调用 Runtime.class?这里又得提到一个类, ConstantTransformer , 这个类的特点就是我们传入啥,它直接就返回啥

这样就能构造最终的 exp:
1 | package com.Fc0; |

以上是其中一条 CC1,还有另一条 CC1,是从 LazyMap 入手,我们也来分析一下,在 LazyMap 的 get 方法里调用了 transform

看构造方法,factory 需要我们控制,同样在类内部找哪里调用了这个构造方法

很明显,跟之前基本相似,就是从 checkValue 换到了 get

那么 get 在哪调用的,还是在 AnnotationInvocationHandler ,它的 invoke 方法调用了 get

这里是个动态代理,我们可以用 AnnotationInvocationHandler 来代理 LazyMap,这样就会触发 invoke 方法,构造一下 exp(基本大差不差):

# 完整利用连:
1 | ObjectInputStream.readObject() |
- Title: JavaSec-CC1
- Author: Fc04dB
- Created at : 2024-09-17 21:28:41
- Updated at : 2024-09-18 12:30:32
- Link: https://redefine.ohevan.com/2024/09/17/JavaSec-CC1/
- License: This work is licensed under CC BY-NC-SA 4.0.