Java安全系列(一):反射
前言
这篇文章即系列的目的在在于将Java以一种中通俗易懂的方式理解,同时记录我自己在学习这块的心得以及自己遇到的坑,来来帮助大家避免。
在讲即这一步分之前,我们需要区分和理解一些东西,来帮助我们学习这一部分内容
之后我会根据我的学习,以此篇文章为开头,来写关于java安全的系列性文章。
Class是什么
Class不是class,Class本身是一个普通类,而Class只是他的名字
我们先来看下面的这两个类
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()...");
}
}
public class MyClass {
public static int z =123;
private int y =100;
public int x = 10;
public static void display() {
System.out.println("Value of x is: " + z);
}
}
我们观察这两个类,这两个类的属性不太,但是特征都是相同的,比如都有类名 都有定义的方法,都有定义的字段,都具有构造方法等等等,一系列的特征
而Class就是java定义的一个专门描述类(类中的方法,定义的变量)的共同特征的一个类
相同的描述不同类中字段的不同类的字段的共同特征的类就叫做Field
类中所有的方法的公共属性,来描述其的类是Method
我们再看Class类
Class这个类中正是借用这这些类来描述一个类的共同特征
也就是说每当我们编译一个新的类时,就会生成一个Class对象(更准确地说,它会被保存在一个同名的.class文件中)。举个例子,如果我们创建一个名为Student的类,那么JVM将会生成一个与Student类对应的Class对象,这个Class对象包含了与Student类相关的类型信息。
当我们实例化一个对象的时候,Javac会把我我们创建的类文件编译成class文件,这里我理解成编译生成了独属于这个People类的Class类(也就是字节码)这里多说一句,我理解的每个类的独属于的Class类其实就是编译后的字节码,然后jvm内存会查找生成的class文件读入内存和经过ClassLoader加载同时会自动创建生成一个Class对象,里面拥有其获取成员变量Field,成员方法Method和构造方法Constructor等方法,最后得到的我们的创建的类的实例化对象了。
好了我们深入的也不做过多的解释了这个解释Class这个类就是为了解除一些误会而引起的影响我们学习的障碍
java反射的概念
Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。
既然有反射相对的那就存在正射
正射就是我们在使用一个类的时候要先知道这个类的方法属性等一系列特征,然后在实例化这个类,而反射就是我们在事前是不知道我们要实例化的什么类的时候,我们就可以通过反射来获得这个类的原型
反射的使用
获取Class类对象
方法一
Person p1 = new Person();
Class c = person.getClass();
方法二
Class c=TestReflection.class
方法三
Class.forName 方法来动态加载驱动类。
Class c=Class.forName("com.test.person");
这里我们来说明以下Class.forName
Class.forName
Class.forName
:返回一个给定类或者接口的一个 Class 对象,它是Class类中的一个静态方法,Class.forName的调用 会返回一个对应类的Class对象 因此 当我们想要去获取一个正在运行的Class对象的相关属性时 我们就可以使用forName方法 获取对Class对象的引用
Class.forName的官方文档的描述存在如下,forName有两种函数重载
Class forName(String name)
Class forName(String name, initialize, ClassLoader loader)
第一种用法,参数name表示类的全名,在第二种用法中,了参数 initialize 的值为 true(默认为ture)loader 的值为当前类使用的类加载器,其中initialize 参数表示是否初始化,而loader参数表示加载器,通过设置不同的加载器,来告诉JVM以什么样的姿态来加载这个类
这里我们可以发现,对于forName方法来说,我们只需要输入完整的类名,就可以实现获取其对象,而并不需要import登操作,这就说明,我们可以利用forName来加载任一类,从而实现攻击,在下面中我们也会展示的。
类的初始化
在研究Class.forName反思所得,因为我们上面说到了Class.forName方法中存在一个布尔型参数,initialize表示类是否进行初始化操作
类的初始化和实例化是这样定义的
类的初始化:
是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次
类的实例化(实例化对象):
是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。(就是调用构造函数)
要注意,初始化就是初始化,实例化就是实例化,但是在实例化一个新类的时候,JVM要从现有的class文件中寻找是否有该类的class,如果没有就会对其进行装载,而这个过程也就是初始化了
初始化会导致类的静态的(变量,方法,代码块)会被执行。如果说存在父类,那么就会先执行父类的,然后再是子类的,请看下面的这个例子
Father.java
public class Father {
private static int j = method();
static {
System.out.println(" 父类静态代码块");
}
Father() {
System.out.println(" 父类无参构造器");
}
{
System.out.println(" 父类代码块");
}
public static int method() {
System.out.println(" 父类 method 方法");
return 1;
}
}
Son.java
public class Son extends Father {
private static int j = method();
static {
System.out.println(" 子类静态代码块");
}
Son() {
System.out.println(" 子类无参构造函数");
}
{
System.out.println(" 子类代码块");
}
public static int method() {
System.out.println(" 子类 method 方法");
return 1;
}
public static void main(String[] args) throws Exception {
Son p2 = new Son();
System.out.println();
Son s2 = new Son();
}
}
可以明显的看出,再首次实例化之前是需要进行初始化的,但是初始化只会进行一次,初始化完事之后,后面的进行实例化就不需要再次初始化了
具有父类的类的实例化:父类静态初始块
->子类静态初始块
->父类初始块
->父类构造函数
->子类初始块
->子类构造函数
获得实例化类的对象
主要是通过newInstance方法
方法一通过Class中的newInstance
Class p=Class.forName("Person");
Object p1=p.newInstance();
Class p=Class.forName("Person");
Person p1=(Person)p.newInstance();
方法二 Constructor中的newInstance
Class cla=p.getClass();
Constructor con = cla.getConstructor();
Object p1 = con.newInstance();
那么为什么要说这两种方法呢?如上第一中方法,只能适用于,我们的类只能含有不含参的构造器,一旦我们的类中没有不含参的构造器的时候第一中方法就会报错了
Class cla=Class.forName("phone");
Constructor con = cla.getConstructor(String.class);
Object p1 = con.newInstance("lituer");
Class.newInstance
newInstance,字面意思就是“新实例”,Class.newInstance是java反射中Class类创建新的实例化对象的方法,在这个过程中,是先取了这个类的不带参数的构造方法,然后调用构造方法 也就是无参构造函数 的 newInstance 来创建对象
public T newInstance()
throws InstantiationException,
IllegalAccessException
Creates a new instance of the class represented by this Class object. The class is instantiated as if by a new expression with an empty argument list. The class is initialized if it has not already been initialized.
Note that this method propagates any exception thrown by the nullary constructor, including a checked exception. Use of this method effectively bypasses the compile-time exception checking that would otherwise be performed by the compiler. The Constructor.newInstance method avoids this problem by wrapping any exception thrown by the constructor in a (checked) InvocationTargetException.
Returns:
a newly allocated instance of the class represented by this object.
Throws:
IllegalAccessException - if the class or its nullary constructor is not accessible.
InstantiationException - if this Class represents an abstract class, an interface, an array class, a primitive type, or void; or if the class has no nullary constructor; or if the instantiation fails for some other reason.
ExceptionInInitializerError - if the initialization provoked by this method fails.
SecurityException - If a security manager, s, is present and any of the following conditions is met:
invocation of s.checkMemberAccess(this, Member.PUBLIC) denies creation of new instances of this class
the caller's class loader is not the same as or an ancestor of the class loader for the current class and invocation of s.checkPackageAccess() denies access to the package of this class
和我们上面说的一样,既然类中存在无参构造函数,那么面对有参构造函数,那么我们就不能使用Class.newInstance而是要用Constructor.newInstance()
然而,通过 Constructor.newInstance() 方法可以根据提供的参数调用任何构造函数。相比之下,Class.newInstance() 方法只能调用可见的构造函数,也就是公共(public)类型的构造函数。然而,在特定情况下,Constructor.newInstance() 方法可以调用私有的构造函数,需要使用 setAccessible(true) 方法来实现。
Class cla=Class.forName("MyClass");
Constructor constructor = cla.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Object obj=constructor.newInstance("lituer");
Method method = cla.getMethod("test");
method.invoke(obj);
执行成功。
获取类的构造器
一:获取public类型的构造器
getConstructor(class[]parameterTypes)//里面的参数可选
getConstructors()//获得类中所有public类型的构造器
实例:就上面用到的
Class cla=Class.forName("phone");
Constructor con = cla.getConstructor(String.class);//获得有参数的
Constructor con = cla.getConstructor();//获得无参数的
Constructor[] con = cla.getConstructors();//获得类中所有public类型的构造器,因为所有的构造器肯定不是一个,所以要用数组
二:getDeclaredConstructor(class[]parameterTypes)
它可以同时获取public和private以及protected还有默认类型的构造器
Class cla=Class.forName("phone");
Constructor con2 = cla.getDeclaredConstructor();
getDeclaredConstructors()方法全部的构造器
Constructor[] con2 = cla.getDeclaredConstructor();
获取类的属性
public class MyClass {
private int z =123;
int y =100;
public String name;
protected int x = 10;
}
1,获得类的public类型的属性
getField(String name)
getFields()//全部public
Class cla=Class.forName("MyClass");
Field field = cla.getField("x");
Field[] field = cla.getFields();
2,获得任一一个类型属性
getDeclaredField(String name)
getDeclaredFields();//全部的任何类型的属性
Class cla=Class.forName("MyClass");
Field field = cla.getDeclaredField("y");
Field[] field = cla.getDeclaredFields();
获取类的方法
1,获得public类型的方法
getMethod(String name,class[] parameterTypes);//第一个参数是方法名,第二个参数是形参的类型,没有即不填
getMethods()
Class cla=Class.forName("MyClass");
Method method = cla.getMethod("test");
Method[] methods = cla.getMethods();
2,获得所有类型的方法
getDeclaredMethod(String name,class[] parameterTypes)
getDeclaredMethods()//全部方法
Class cla=Class.forName("MyClass");
Method method = cla.getDeclaredMethod("input", String.class);
Method[] methods=cla.getDeclaredMethods();
!
获得Method信息包括
- getName() 返回方法的名称
- getReturnType() 返回方法返回值类型 也是一个 Class实例比如 String.class
- getParameterTypes():返回方法的参数类型,是一个 Class 数组,例如:{String.class, int.class};
- getModifiers():返回方法的修饰符,它是一个 int,不同的 bit 表示不同的含义
获得类方法的调用
使用invoke
Object invoke(Object obj,Object...args)
其中参数 obj 是实例化后的对象,args 为用于方法调用的参数,如果我们获取的方法为非静态方法,那么第一个参数是类对象,如果是一个静态方法,那么我第一个参数就是一个类
这也比较好理解了,我们正常执行方法是 [1].method([2], [3], [4]...) ,其实在反射里就是 method.invoke([1], [2], [3], [4]...) 。
下面是整个反射利用的过程
Class cla=Class.forName("MyClass");//获得Class对象
Constructor constructor =cla.getConstructor(String.class);//获得构造器
Object obj=constructor.newInstance("lituer");//实例化对象
Method method = cla.getMethod("test");//获得方法
method.invoke(obj);//执行方法
获得类属性的修改
我们使用set方法
我们当修改我们类中的属性可以通过
Class cla=Class.forName("MyClass");
Field field = cla.getField("x");
先获得属性,然后通过set来修改
Class cla=Class.forName("MyClass");
Field field = cla.getField("x");
field.set(p,"xiaoming");//这个p是Class类的实例对象
如果说是私有属性 的话我们怎么修改
第一步我们需要获得这个属性我们使用getDeclaredField
Class cla=Class.forName("MyClass");
Field field = cla.getDeclaredField("y");
然后我们还需要setAccessible
Class cla=Class.forName("MyClass");
Field field = cla.getDeclaredField("y");
field.setAccessible(true);
field.set(p,"xiaoming");