java反射机制的定义

java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法称为java语言的反射机制
反射是大多数语言里都必不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),总之通过"反射",我们可以将Java这种静态语言附加上动态特性

反射里的重要方法

获取类的方法:forName
获取实例化对象的方法:newInstance
获取函数的方法:getMethod
执行函数的方法:invoke

获取class对象的方法

1.类的.class属性(需要导入类的包)

Class c1 = Demo.class;

2.实例化对象的getClass方法(需要本身创建一个对象)

Demo demo = new Demo();
Class c2 = demo.getClass();

3.动态加载类

Class c3 =Class.forName('com.company.Demo');

forname有两个重载

Class.forName(className)
// 等于
Class.forName(className,true,currentLoader)

默认情况下,forName的第一个参数是类名;第二个参数表示是否初始化(类的初始化);第三个参数就是ClassLoader

ClassLoader就是一个"加载器",告诉JVM如何去加载这个类。Java默认的ClassLoader就是根据类名来加载类,这个类名就是完整路径。

java.lang.Runtime

获取成员变量

Field getField(String name) 获取指定名称的 public修饰的成员变量

Field getDeclaredField(String name) 获取指定的成员变量

Field[] getFields() :获取所有public修饰的成员变量

Field[] getDeclaredFields() 获取所有的成员变量(包括私有)

获取成员方法

Method getMethod(String name,Class<?>... parameterTypes) //返回该类所声明的public方法

Method getDeclaredMethod(String name,Class<?>... parameterTypes) //返回该类所声明的所有方法

//第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型

Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法

Method[] getDeclaredMethods() // 获取该类中的所有方法

获取构造函数

Constructor<?>[] getConstructors()  返回所有public构造函数

Constructor<?>[] getDeclaredConstructors()  返回所有构造函数

Constructor<> getConstructor(Class<?>... parameterTypes) : 匹配和参数配型相符的public构造函数

Constructor<> getDeclaredConstructor(Class<?>... parameterTypes) : 匹配和参数配型相符的所有构造函数

利用反射创建类对象

Person类

import java.io.Serializable;

public class Person implements Serializable {
    public String name;
    public int age;
    public Person(){}
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name=" + '\'' + name + '\'' +
                ", age=" + age +
                '}';
    }
}

无参构造:

public class ReflectionTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Person person = new Person();
        Class c = Class.forName("Person");
        Person p =(Person) c.newInstance();
        System.out.println(p);
    }
}

带参数的构造:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ReflectionTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class c = Class.forName("Person");
        Constructor personconstructor = c.getConstructor(String.class,int.class );
        Person p =(Person) personconstructor.newInstance("abc",12);
        System.out.println(p);
    }
}

在正常情况下,除了系统类,如果我们想拿到一个类,需要先import才能使用。而使用forName就不需要,这对于攻击者来说就非常有利,可以加载任意类

另外,我们经常在一些源码里看到,类名的部分包含$符号,$的作用是查找内部类

Java的普通类c1中支持编写内部类c2,而在编译的时候,会生成两个文件:c1.classc1$c2.class,我们可以把他们看作两个无关的类,通过class.forName("c1$c2")即可加载这个内部类。

获得类以后,我们可以继续用反射来获取这个类中的属性,方法,也可以实例化这个类并调用方法

class.newInstance()的作用就是调用这个类的无参构造函数,但是有时候在写漏洞利用方法的时候,会发现使用newInstance总是不成功,原因可能是:1.使用的类没有无参构造函数;2.使用的类的构造函数是私有的

最常见的情况是java.lang.Runtime,这个类在我们构造payload时很常见,但我们不能直接这样来执行命令

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.newInstance(),"id");

原因是Runtime类的构造方法是私有的,因为Runtime类是单例模式,我们只能通过Runtime.getRuntime()来获取到Runtime对象。

Class clazz = class.forName("java.lang.Runtime");
clazz.getMethod("exec","String.class").invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");

分解一下就是

Class clazz = class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec",String.class);
Method getRuntime = clazz.getMethod("getRuntime");
Object runtime = getRuntime.invoke(clazz);
execMethod.invoke(runtime,"calc.exe");

和getMethod类似,getConstructor接受的参数是构造函数列表类型,因为构造函数也支持重载,所以必须用参数列表类型才能唯一确定一个构造函数,获取到构造函数之后,我们使用newInstance来执行

我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后调用start()来执行命令

Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();

我们之前的payload也可以改成

Class clazz  = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec",String.class).invoke(m.newInstance(),"calc.exe");

Q.E.D.