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的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么就必须严格的依次传入对应的参数类型