反射

时间:2022-10-21 22:03:36

1.简介

Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。由此反射被称为框架的灵魂,
最终操作的是字节码文件(可以读和修改字节码文件),java反射机制的相关类在java.lang.reflect.*包下

2.反射工作原理

当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。反射的工作原 理就是借助Class.javaConstructor.javaMethod.javaField.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的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么就必须严格的依次传入对应的参数类型