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.