java反射机制
本篇文章记录java的反射机制,虽然现在还不知道对渗透安全来说有什么好处,管他的,就是学@@
什么是反射
反射这个词啊,就是事物对外部动作刺激作出的相应反馈。今天学习的是java反射,那这又是什么东东呢?跟着小透来领略java带来的震撼和有趣吧!
Java反射机制就是在程序运行状态中,动态调用任意一个类及其该类的所有属性和方法。(说实话,我TM看到这个解释的时候,一脸???)
下面我们将反射机制和直接类对象初始化作一个对比:
下面是自己写的helloworld类
1 2 3 4 5 6 7 8 9
| package com.fanseTest;
public class helloworld { public void hello(String str) { System.out.println(str.toString()); } }
|
1 2 3 4 5
| public static void main(String[] args) { helloworld test = new helloworld(); test.hello("helloworld"); }
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.fanseTest;
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class fanse { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class test = Class.forName("com.fanseTest.helloworld"); Method test1 = test.getMethod("hello", String.class); Constructor constructor = test.getConstructor(); Object test2 = constructor.newInstance(); test1.invoke(test2, "helloworld"); } }
|
运行结果:
我们可以从上面两个的对比来看,第一种是在运行前将类实例化成对象并调用方法赋值,第二种反射机制是在运行时动态调用方法和属性赋值。
反射的利弊
Java反射机制(Reflection)是java非常重要的动态特性,我们不仅可以通过反射获取到任何类的方法(Methods),成员变量(Fields),构造方法(Constructors)等信息,还能动态创建任何类实例,
优点
可扩展性
应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
类浏览器和可视化开发环境
一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
调试器和测试工具
调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
缺点
性能开辟
反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
安全限制
使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet。
内部暴露
由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
获取Class对象
我们可以从上面的反射代码中观察到,第一句代码的操作是获取类Class对象,所以这里简单记录下获取Class对象发方法:
类名.class.
如:com.fanseTest.helloworld.class
Class.formName(“类名”).
如:Class.forName(“com.fanseTest.helloworld”)
对象名.getClass().
如:helloworld test = new helloworld(); hello.getClass()
classloader.loadClass(“类名”)如:ClassLoader.getSystemClassLoader().loadClass(“com.fanseTest.helloworld”)
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.fanseTest;
public class getClass { public static void main(String[] args) throws ClassNotFoundException {
String str = "com.fanseTest.helloworld"; Class test = helloworld.class; Class test1 = Class.forName("com.fanseTest.helloworld"); Class test2 = ClassLoader.getSystemClassLoader().loadClass("com.fanseTest.helloworld"); System.out.println(test.toString()); System.out.println(test1.toString()); System.out.println(test2.toString()); helloworld test3 = new helloworld(); System.out.println(test3.getClass().toString()); } }
|
运行结果:
值的注意的是,当我们获取数组类型的Class对象时,使用java类型描述符(类型描述符相关介绍参见JAVA类型描述符 | 小透的少年江湖 (haoyun.fit))
举个栗子:
1 2 3 4 5 6
| Class<?> booleanArray = Class.forname("[Z");//获取布尔数组类对象 Class<?> doubleArray = Class.forname("[D");//获取浮点数组类对象 Class<?> byteArray = Class.forname("[B");//获取字节数组类对象 Class<?> classArray = Class.forname("[Lclassname;"); Class<?> cstringArray = Class.forname("[Ljava.lang.String;");//获取字符串数组类对象 //[符号根据数组维度来添加
|
注:
反射 调用内部类的时候需要使用$来代替.,如com.reflect.ReflectTest类中有一个叫做Hello的内部类,调用时将类名写成:com.reflect.ReflectTest$Hello。
RunTime反射
学习了什么是java反射机制和java反射机制的优缺点,我就有了一个疑问?对于渗透测试代码审计来说,java反射的作用是什么?或者说怎么利用这个机制进行测试。在一些反序列化漏洞执行系统命令时可以调用Runtime等类执行本地命令编写漏洞利用代码、代码审计、绕过RASP方法等,还有后面要学习的内存马等。
本地命令执行
执行本地命令:
1 2 3 4 5 6 7 8 9 10 11
| package com.fanseTest;
import sun.misc.IOUtils; import java.lang.Runtime;
public class zhixing { public static void main(String[] args) { System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(),"UTF-8")); } }
|
反射Demo1:(注:记得添加org.apache.commons.io程序包)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.fanseTest;
import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class zhixing { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException { Class runtimeClass1 = Class.forName("java.lang.Runtime");
String cmd = "whoami"; Constructor constructor = runtimeClass1.getDeclaredConstructor(); constructor.setAccessible(true);
Object runtimeInstance = constructor.newInstance();
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);
InputStream in = process.getInputStream();
System.out.println(IOUtils.toString(in, "UTF-8")); } }
|
运行结果:
返回当前用户为:”desktop-3n366og\administrator”。
反射 Demo2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.fanseTest;
import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException;
public class reflect { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException { Class runtimeClass1 = Class.forName("java.lang.Runtime"); String cmd = "whoami";
Object runtime = runtimeClass1.getMethod("getRuntime").invoke(runtimeClass1);
Process process = (Process) runtimeClass1.getMethod("exec",String.class).invoke(runtime,cmd);
InputStream in = process.getInputStream();
System.out.println(IOUtils.toString(in, "UTF-8")); } }
|
这两个代码都是反射调用Runtime类的exec()方法执行本地命令执行。但两者不同的是,Demo1获取Runtime类对象是通过构造器创建构造方法来获取,而Demo2是通过Runtime类的getRuntime()方法来获取。
调用流程
反射调用Runtime实现本地命令执行的流程如下:
- 反射获取
Runtime
类对象(如Class.forName("java.lang.Runtime")
)
- 使用
Runtime
类的Class对象获取Runtime
类的无参数构造方法(getDeclaredConstructor()
),因为Runtime
的构造方法是private
的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true)
)
- 获取
Runtime
类的exec(String)
方法(runtimeClass1.getMethod("exec", String.class);
)
- 调用
exec(String)
方法(runtimeMethod.invoke(runtimeInstance, cmd)
)