1.简介
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。由此反射被称为框架的灵魂,
最终操作的是字节码文件(可以读和修改字节码文件),java反射机制的相关类在java.lang.reflect.*包下
2.反射工作原理
当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。反射的工作原 理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。
3.反射机制相关类
| 类 | 含义 |
|---|---|
| java.lang.Class | 代表整个类 (整个字节码) |
| java.lang.reflect.Method | 代表类中的方法 (方法字节码) |
| java.lang.reflect.Constructor | 代表类中的构造方法 (构造方法字节码) |
| java.lang.reflect.Field | 代表类中的成员变量(静态变量+实例变量) |
注:必须先获得Class才能获取Method、Constructor、Field
4.实例
下面是一个基本类
package com.gk0d.reflect; public class Person { //私有属性 private String name = "Tom"; //公有属性 public int age = 18; //构造方法 public Person() { } //私有方法 private void say(){ System.out.println("private say()..."); } //公有方法 public void work(){ System.out.println("public work()..."); } }
4.1通过反射实例化对象
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
// 通过反射机制,获取Class,通过Class来实例化对象 Class c = Class.forName("com.gk0d.reflect.Person"); // newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。 // 重点是:newInstance()调用的是无参构造,所以必须保证无参构造是存在的! Object obj = c.newInstance();
- 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象 Class<?> c = String.class; //获取String类带一个String参数的构造器 Constructor constructor = c.getConstructor(String.class); //根据构造器创建实例 Object obj = constructor.newInstance("23333"); System.out.println(obj);
4.2获取方法
获取某个Class对象的方法集合,主要有以下几个方法:
getDeclaredMethods方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
public Method[] getDeclaredMethods() throws SecurityException
getMethods方法返回某个类的所有公用(public)方法,包括其继承类的公用方法
public Method[] getMethods() throws SecurityException
- getMethod 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
public Method getMethod(String name, Class<?>... parameterTypes)
4.3获取构造器信息
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs)
此方法可以根据传入的参数来调用对应的Constructor创建对象实例
4.4获取类的成员变量信息
主要是这几个方法,:
- getFiled:访问公有的成员变量
- getDeclaredField:所有已声明的成员变量,但不能得到其父类的成员变量
- getFileds 和 getDeclaredFields 方法用法同上(参照 Method)。
4.5调用方法
当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。invoke 方法的原型为:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
下面是一个实例
public class test1 { public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class<?> klass = methodClass.class; //创建methodClass的实例 Object obj = klass.newInstance(); //获取methodClass类的add方法 Method method = klass.getMethod("add",int.class,int.class); //调用method对应的方法 => add(1,4) Object result = method.invoke(obj,1,4); System.out.println(result); } } class methodClass { public final int fuck = 3; public int add(int a,int b) { return a+b; } public int sub(int a,int b) { return a+b; }
5.利用
反射的利用主要是调用一些危险函数,例如Runtime.exec方法可以本地执行命令,大部分关于jsp命令执行的payload可能都是调用此方法进行命令执行的。
5.1普通执行命令
import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; public class Main { public static void main(String[] args) throws IOException { InputStream ipconfig = Runtime.getRuntime().exec("ipconfig").getInputStream(); String s = IOUtils.toString(ipconfig,"gbk"); //使用IOUtils.toString静态方法将字节输入流转换为字符 System.out.println(s); } }
缺点就是代码是静态的,并不能做到类似webshell的效果
5.2利用反射执行命令
import org.apache.commons.io.IOUtils; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class Test2 { public static void main(String[] args) throws Exception { String command = "ipconfig"; Class cls = Class.forName("java.lang.Runtime"); //Runtime加载进内存 Constructor declaredConstructor = cls.getDeclaredConstructor(); //获取构造方法, declaredConstructor.setAccessible(true); //暴力反射,因为JDK的安全检查耗时较多.所以这种方式方式关闭安全检查,达到提升反射速度的目的 Object o = declaredConstructor.newInstance(); //创建Runtime类 Method exec = cls.getMethod("exec", String.class); //获取exec方法,设置需要参数string类型参数 Process process = (Process) exec.invoke(o,command); //执行exec方法,并传入ipconfig参数 InputStream inputStream = process.getInputStream(); //获取输出的数据 String ipconfig = IOUtils.toString(inputStream,"gbk"); //字节输出流转换为字符 System.out.println(ipconfig); } }
method.invoke的第一个参数必须是类实例对象,如果调用的是static方法那么第一个参数值可以传null,因为在java中调用静态方法是不需要有类实例的,因为可以直接类名.方法名(参数)的方式调用。
method.invoke的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么就必须严格的依次传入对应的参数类型