一、Java 反射基础
Java 的反射(reflection)机制是指在程序运行中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。 这种动态获取程序信息以及动态调用对象的功能称为 Java语言的反射机制 。
反射机制允许我们在 Java程序运行时检查,操作或者说获取任何类、接口、构造函数、方法和字段。还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等操作。
在 Java代码审计中学习反射机制,我们目的是可以利用反射机制操作目标方法执行系统命令。比如我们想要反射调用 java.lang.runtime 去执行系统命令。
1.1 获取Class对象:
获取 Class 对象的方式有下面几种:
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全路径类名)
- 通过类加载器获得class对象: ClassLoader.getSystemClassLoader().loadClass(“com.example.xxx”);
需要注意:
类名.class:需要导入类的包
对象.getClass():初始化对象后,其实不需要再使用反射了。
Class.forName(全路径类名):需要知道类的完整全路径,这是我们常使用的方法
通过类加载器获得class对象:
ClassLoader.getSystemClassLoader().loadClass(“com.example.xxx”)
Class.forName() 获取 class 对象方法是常用的一种方式,下面所有示例代码我们都使用Class.forName() 这个方法来获取Class对象。
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
| package com.example.demo; import com.example.demo.entity.User;
public class GetClass { public static void main(String[] args) throws ClassNotFoundException { //1.通过类名.class Class c1 = User.class;
//2.通过对象的getClass()方法 User user = new User(); Class c2 = user.getClass();
//3.通过 Class.forName()获得Class对象; Class c3 = Class.forName("com.example.demo.entity.User");
//4.通过类加载器获得class对象 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class c4 = classLoader.loadClass("com.example.demo.entity.User");
System.out.println(c1); System.out.println(c2); System.out.println(c3); System.out.println(c4); } }
|
1.2 Java 反射 API
Java 提供了一套反射 API,该API由 Class 类与 java.lang.reflect 类库组成。
该类库包含了 Field 、 Method 、 Constructor 等类。
1.2.1 java.lang.Class (描述类的内部信息)
用来描述类的内部信息, Class 的实例可以获取类的包、注解、修饰符、名称、超类、接口等。
| 方法名 |
释义 |
| getPackage() |
获取该类的包 |
| getDeclaredAnnotations() |
获取该类上所有注解 |
| getModifiers() |
获取该类上的修饰符 |
| getName() |
获取类名称 |
| getSimpleName() |
获取简单类名称 |
| getGenericSuperclass() |
获取直属超类 |
| getGenericInterfaces() |
获取直属实现的接口 |
| newInstance() |
根据构造函数创建一个实例 |

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 31 32
| package com.example.demo.reflectdemo; import java.lang.reflect.Modifier;
public class ClassDemo {
public static void main(String[] args) throws ClassNotFoundException { Class clazz = Class.forName("com.example.demo.reflectdemo.UserInfo"); Package aPackage = clazz.getPackage(); System.out.println("getPackage运行结果:" + aPackage);
int modifiers = clazz.getModifiers(); String modifier = Modifier.toString(modifiers); System.out.println("getModifiers运行结果:" + modifier);
String name = clazz.getName(); System.out.println("getName运行结果:" + name);
String simpleName = clazz.getSimpleName(); System.out.println("getSimpleName运行结果:" + simpleName); } }
getPackage运行结果:package com.example.demo.reflectdemo getModifiers运行结果:public getName运行结果:com.example.demo.reflectdemo.UserInfo getSimpleName运行结果:UserInfo
|
1.2.2 获取属性信息
java.lang.reflect.Field 提供了类的属性信息。可以获取属性(字段)上的注解、修饰符、属性类型、属性名等。
| 方法名 |
释义 |
| getField(“xxx”) |
获取指定名称的公有字段(即使用 public 修饰的字段)的声明信息 |
| getFields() |
获取该类以及其父类中所有的公有字段的声明信息 |
| getDeclaredField(“xxx”) |
获取指定名称的任意字段的声明信息,无论其访问权限是公有的还是私有的。 |
| getDeclaredFields() |
获取该类中所有声明的字段的声明信息,包括公有的、受保护的、默认访问权限的和私有的字段,但不包括父类中的字段 |
| getName() |
返回字段的名称。 |
| getType() |
返回字段的类型 |
| get() |
返回指定对象上该字段的值 |
| set() |
将指定对象上的该字段设置为指定的新值 |
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package com.example.demo.reflectdemo; import java.lang.reflect.Field; import java.lang.reflect.Modifier;
public class FieldDemo { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
// 获取一个该类或父类中声明为 public 的属性 Field field1 = clazz.getField("age"); System.out.println("getField运行结果:" + field1); //System.out.println("getName运行结果:" + field1.getName()); //System.out.println("getType运行结果:" + field1.getType());
// 获取该类及父类中所有声明为 public 的属性 Field[] fieldArray1 = clazz.getFields(); for (Field field : fieldArray1) { System.out.println("getFields运行结果:" + field); }
// 获取一个该类中声明的属性 Field field2 = clazz.getDeclaredField("name"); System.out.println("getDeclaredField运行结果:" + field2);
// 获取某个属性的修饰符(该示例为获取上面name属性的修饰符) String modifier = Modifier.toString(field2.getModifiers()); System.out.println("getModifiers运行结果: " + modifier);
// 获取该类中所有声明的属性 Field[] fieldArray2 = clazz.getDeclaredFields(); for (Field field : fieldArray2) { System.out.println("getDeclaredFields运行结果:" + field); } } }
// getField运行结果:public int com.example.demo.reflectdemo.UserInfo.age getFields运行结果:public int com.example.demo.reflectdemo.UserInfo.age getFields运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.address getDeclaredField运行结果:private java.lang.String com.example.demo.reflectdemo.UserInfo.name getModifiers运行结果: private getDeclaredFields运行结果:private java.lang.String com.example.demo.reflectdemo.UserInfo.name getDeclaredFields运行结果:public int com.example.demo.reflectdemo.UserInfo.age getDeclaredFields运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.address
|
1.2.3 获取方法信息
java.lang.reflect.Method 提供了类的方法信息。可以获取方法上的注解、修饰符、返回值类型、方法名称、所有参数。
| 方法名 |
释义 |
| getDeclaredMethods() |
获取所有在该类中声明的方法,无论其访问修饰符是什么。这个方法返回一个包含 Method 对象的数组 |
| getDeclaredMethod() |
获取一个在该类中声明的方法,无论其访问修饰符是什么 |
| getMethod(“setAge”,String.class) |
获取目标类及父类中声明为 public 的方法,需要指定方法的入参考型 |
| getMethods() |
获取该类及父类中所有声明为 public 的方法 |
| getParameters() |
获取所有传参 |
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| package com.example.demo.reflectdemo; import java.lang.reflect.Method; import java.lang.reflect.Parameter;
public class MethodDemo { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
// 获取所有在该类中声明的方法 // getDeclaredMethods() 获取的是所有方法,而不是单个指定的方法。这个方法通常用于通过反射来动态地了解类的结构和调用其中的方法。 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method temp : declaredMethods) { System.out.println("01-getDeclaredMethods运行结果:" + temp); }
// 获取一个在该类中声明的方法, // 这个方法通常用于通过反射来 动态地调用类中的方法。通过提供方法名和参数类型列表,你可以获取一个 Method 对象,然后可以使用这个对象来调用相应的方法。 Method declaredMethod = clazz.getDeclaredMethod("getName"); System.out.println("02-getDeclaredMethod运行结果:" + declaredMethod); Method declaredMethod2 = clazz.getDeclaredMethod("introduce"); System.out.println("03-getDeclaredMethod运行结果- 2:" + declaredMethod2);
// 获取一个该类及父类中声明为 public 的方法,需要指定方法的入参类型 Method method = clazz.getMethod("setName", String.class); System.out.println("04-getMethod运行结果:" + method); // 获取所有入参 Parameter[] parameters = method.getParameters(); for (Parameter temp : parameters) { System.out.println("05 - getParameters运行结果 " + temp); }
// 获取该类及父类中所有声明为 public 的方法 Method[] methods = clazz.getMethods(); for (Method temp : methods) { System.out.println("06-getMethods运行结果:" + temp); }
} }
// 01-getDeclaredMethods运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.toString() 01-getDeclaredMethods运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.getName() 01-getDeclaredMethods运行结果:public void com.example.demo.reflectdemo.UserInfo.setName(java.lang.String) 01-getDeclaredMethods运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.sayHello() 01-getDeclaredMethods运行结果:private java.lang.String com.example.demo.reflectdemo.UserInfo.introduce() 01-getDeclaredMethods运行结果:public void com.example.demo.reflectdemo.UserInfo.setAge(int) 01-getDeclaredMethods运行结果:public int com.example.demo.reflectdemo.UserInfo.getAge() 02-getDeclaredMethod运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.getName() 03-getDeclaredMethod运行结果- 2:private java.lang.String com.example.demo.reflectdemo.UserInfo.introduce() 04-getMethod运行结果:public void com.example.demo.reflectdemo.UserInfo.setName(java.lang.String) 05 - getParameters运行结果 java.lang.String arg0 06-getMethods运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.toString() 06-getMethods运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.getName() 06-getMethods运行结果:public void com.example.demo.reflectdemo.UserInfo.setName(java.lang.String) 06-getMethods运行结果:public java.lang.String com.example.demo.reflectdemo.UserInfo.sayHello() 06-getMethods运行结果:public void com.example.demo.reflectdemo.UserInfo.setAge(int) 06-getMethods运行结果:public int com.example.demo.reflectdemo.UserInfo.getAge() 06-getMethods运行结果:public final void java.lang.Object.wait() throws java.lang.InterruptedException 06-getMethods运行结果:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException 06-getMethods运行结果:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException 06-getMethods运行结果:public boolean java.lang.Object.equals(java.lang.Object) 06-getMethods运行结果:public native int java.lang.Object.hashCode() 06-getMethods运行结果:public final native java.lang.Class java.lang.Object.getClass() 06-getMethods运行结果:public final native void java.lang.Object.notify() 06-getMethods运行结果:public final native void java.lang.Object.notifyAll() 进程已结束,退出代码0
|
1.2.4 获取修饰信息
java.lang.reflect.Modifier 提供了访问修饰符信息。通过 Class 、 Field 、 Method 、 Constructor 等。对象都可以获取修饰符,这个访问修饰符是一个整数,可以通过 Modifier.toString 方法来查看修饰符描述。并且该类提供了一些静态方法和常量来解码访问修饰符。
| 方法名 |
释义 |
| getModifiers() |
获取类的修饰符值 |
| getDeclaredField(“username”).getModifiers() |
获取属性的修饰符值 |
| ……. |
|
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 31 32
| package com.example.demo.reflectdemo; import java.lang.reflect.Modifier;
public class ModifierDemo { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
int modifiers1 = clazz.getModifiers(); System.out.println("01 - 获取类的修饰符值getModifiers运行结果:" + modifiers1);
int modifiers2 = clazz.getDeclaredField("name").getModifiers(); System.out.println("02 - 获取属性的修饰符值getModifiers运行结果:" + modifiers2);
int modifiers4 = clazz.getDeclaredMethod("setName", String.class).getModifiers(); System.out.println("03 - 获取方法的修饰符值getModifiers运行结果:" + modifiers4);
String modifier = Modifier.toString(modifiers1); System.out.println("04 - 获取类的修饰符值的字符串结果:" + modifier); System.out.println("05 - 获取属性的修饰符值字符串结果:" + Modifier.toString(modifiers2)); } }
01 - 获取类的修饰符值getModifiers运行结果:1 02 - 获取属性的修饰符值getModifiers运行结果:2 03 - 获取方法的修饰符值getModifiers运行结果:1 04 - 获取类的修饰符值的字符串结果:public 05 - 获取属性的修饰符值字符串结果:private
|
1.2.5 获取构造函数信息
java.lang.reflect.Constructor 提供了类的构造函数信息。可以获取构造函数上的注解信息、参数类型等。
| 方法名 |
释义 |
| getConstructor() |
获取一个声明为 public 构造函数实例 |
| getConstructors() |
获取所有声明为 public 构造函数实例 |
| getDeclaredConstructor() |
获取一个声明的所有修饰符的构造函数实例 |
| getDeclaredConstructors() |
获取所有声明的所有修饰符的构造函数实例 |
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.example.demo.reflectdemo; import java.lang.reflect.Constructor; public class ConstructorDemo { public static void main(String[] args) throws Exception{
Class<?> clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
// 获取一个声明为 public 构造函数实例,入参设置了获取的是 String.class,int.class 这个 Constructor<?> constructor1 = clazz.getConstructor(String.class,int.class); System.out.println("1-getConstructor运行结果:" + constructor1); // 获取的是无参 的 public 构造函数 //Constructor<?> constructor666 = clazz.getConstructor(); //System.out.println("666-getConstructor运行结果:" + constructor666); // 根据构造函数创建一个实例 Object c1 = constructor1.newInstance("power7089",18); //类似 UserInfo userinfo = new UserInfo("power7089",18); System.out.println("2-newInstance运行结果: " + c1);
// 获取所有声明为 public 构造函数实例 Constructor<?>[] constructorArray1 = clazz.getConstructors(); for (Constructor<?> constructor : constructorArray1) { System.out.println("3-getConstructors运行结果:" + constructor); }
// 获取一个声明的构造函数实例 Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class); System.out.println("4-getDeclaredConstructor运行结果:" + constructor2); // 将构造函数的可访问标志设为 true 后,可以通过私有构造函数创建实例 constructor2.setAccessible(true); Object o2 = constructor2.newInstance("Power7089666"); System.out.println("5-newInstance运行结果:" + o2); //Constructor<?> constructor888 = clazz.getDeclaredConstructor(); //System.out.println("888-newInstance运行结果:" + constructor888);
// 获取所有声明的构造函数实例 Constructor<?>[] constructorArray2 = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructorArray2) { System.out.println("6-getDeclaredConstructors运行结果:" + constructor); } } }
// 1-getConstructor运行结果:public com.example.demo.reflectdemo.UserInfo(java.lang.String,int) 2-newInstance运行结果: Person{name='power7089', age=18} 3-getConstructors运行结果:public com.example.demo.reflectdemo.UserInfo(java.lang.String,int) 3-getConstructors运行结果:public com.example.demo.reflectdemo.UserInfo() 4-getDeclaredConstructor运行结果:private com.example.demo.reflectdemo.UserInfo(java.lang.String) 5-newInstance运行结果:Person{name='Power7089666', age=0} 6-getDeclaredConstructors运行结果:public com.example.demo.reflectdemo.UserInfo(java.lang.String,int) 6-getDeclaredConstructors运行结果:private com.example.demo.reflectdemo.UserInfo(java.lang.String) 6-getDeclaredConstructors运行结果:public com.example.demo.reflectdemo.UserInfo()
|
1.2.6 获取参数信息
java.lang.reflect.Parameter 提供了方法的参数信息。可以获取方法上的注解、参数名称、参数类型等。
1
| getParameters() 获取构造函数/方法的参数
|
1.2.7 绕过私有限制(ProcessImpl命令执行也有涉及)
java.lang.reflect.AccessibleObject 是 Field 、 Method 和 Constructor 类的超类。该类提供了对类、方法、构造函数的访问控制检查的能力(如:私有方法只允许当前类访问)。
| 方法名 |
释义 |
| setAccessible() |
将可访问标志设为 true (默认为 false ),会关闭访问检查。这样即使是私有的属性、方法或构造函数,也可以访问。 |
1 2 3 4 5 6 7 8 9
| // 获取一个声明的构造函数实例 Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class); System.out.println("4-getDeclaredConstructor运行结果:" + constructor2); // 将构造函数的可访问标志设为 true 后,可以通过私有构造函数创建实例 constructor2.setAccessible(true); Object o2 = constructor2.newInstance("Power7089666"); System.out.println("5-newInstance运行结果:" + o2); //Constructor<?> constructor888 = clazz.getDeclaredConstructor(); //System.out.println("888-newInstance运行结果:" + constructor888);
|
常用方法整理
getMethod()
getMethod()方法获取的是当前类中所有公共(public)方法。包括从父类里继承来的方法。
getDeclaredMethod()
getDeclaredMethod()系列方法获取的是当前类中“声明”的方法,包括private,protected和public,不包含从父类继承来的方法。
getConstructor()
getConstructor()方法获取的是当前类声明为公共(public)构造函数实例
getDeclaredConstructor()
getDeclaredConstructor() 方法获取的是当前类声明的构造函数实例,包括private,protected 和 public。
setAccessible()
在获取到私有方法或构造方法后,使用 setAccessible(true); ,改变其作用域,这样即使是私有的属性,方法,构造函数也都可以访问调用了。
newInstance()
将获取到的对象实例化。调用的是这个类的无参构造函数,或者有参构造函数需要设定传参。
他和 new 关键字去实例化对象相似,而 newInstance() 是个函数方法,而 new 是个关键字,这是有区别的。
使用 newInstance 不成功的话可能是因为:①、你使用的类没有无参构造函数,②、你使用的类构造函数是私有的。
当然了,调用无参构造函数使用方法是,Class.newInstance()。
调用带参数的构造函数,则是通过 Class 类获取 Constructor,最后调用 Constructor 中的newInstance(Object … initarges) 方法,这里面 newInstance 中需要传入所需的参数。
invoke()
调用包装在当前Method对象中的方法。
二. Java 反射到命令执行
学习ava反射机制,其实我们更关心如何利用Java反射实现命令执行。下面举例讲解下Java反射命令执行的几种情况。
2.1 反射之 Java.lang.Runtime
下面是两种通过反射java.lang.Runtime来达到命令执行的方式。
2.1.1 方式一:通过 getMethod
由于java.lang.Runtime类的构造函数是私有的,因此不能直接使用 newInstance() 创建一个实例。
由于 java.lang.Runtime 使用了单例模式,我们可以通过 Runtime.getRuntime() 来获取 Runtime 对象。
1 2 3 4 5 6 7 8 9
| public class RuntimeGetMethod { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("java.lang.Runtime"); Method execMethod = clazz.getMethod("exec", String.class); Method getRuntimeMethod = clazz.getMethod("getRuntime"); Object runtime = getRuntimeMethod.invoke(null); execMethod.invoke(runtime, "calc.exe"); } }
|
- 首先通过 Class.forName 获取 java.lang.Runtime。
- 接下来通过 getMethod() 方法获 exec 方法,在 java 命令执行章节中我们了解到, exec() 方法有六种调用方式(重载),我们选择最简单的 String 方式,则 getMethod 方法我们设定的入参方式为 String.class
- 然后获取 getRuntime 方法后,使用 invoke 执行方法。
- 最后在通过 invoke 方法调用 runtime 对象执行命令。
2.1.1 方式二:通过 getDeclaredConstructor
如果方法或构造函数是私有的,我们可以使用 getDeclaredMethod 或 getDeclaredConstructor 来获取执行。
在这里,java.lang.Runtime 的构造函数为私有的,因此我们可以使用 getDeclaredConstructor方法获取java.lang.Runtime并执行。
1 2 3 4 5 6 7
| Class<?> clazz = Class.forName("java.lang.Runtime"); Constructor m = clazz.getDeclaredConstructor(); System.out.println(m); m.setAccessible(true); Method c1 = clazz.getMethod("exec", String.class); System.out.println(c1); c1.invoke(m.newInstance(), "calc.exe");
|
- 首先通过Class.forName 获取 java.lang.Runtime
- 接下来通过 getDeclaredConstructor 获取构造函数
- 通过 setAccessible(true) 设置改变作用域,让我们可以调用他的私有构造函数
- 调用exec方法,入参设置为 String.class
- 最后使用Invoke执行方法
2、反射之 java.lang.ProcessBuilder
如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们可以通过 getConstructor() 方法实例化该类。当然也可以使用 getDeclaredConstructor() 方法。
1 2 3 4 5 6 7
| public class ProcessBuilderGetConstructor { public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("java.lang.ProcessBuilder"); Object object = clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")); clazz.getMethod("start").invoke(object,null); } }
|