Java安全--篇1-反射

Java安全–篇1-反射

0x00前言

看了好多文章,一直没搞懂反射是什么,然后闲着无聊搜了视频,一看就懂了好多,这里浅说一下。很多顺序是跟着P神的文章走的,把自己看不懂的百度出来记录一下。

0x01之前的关注点

之前我们关注类都是关心他的构造方法,属性,方法。更多的是关注实例化出来的对象,可以调用什么属性,什么方法。如下图所示:

我们都知道,类是用来描述一组对象,类实例化之后就是一个对象。

类是对象的模板,而对象是类的实例化。

引出第一句话:

对象可以通过反射获取他的类

而这个深灰色所包括的三个对象是同一个类实例化出来的,他们的模板都很像,那对象是这样,属性是否也是这样呢?

0x02把目光聚集在所有类上

现在把目光上升一层,之前关注的是对象,现在把这个灰色框框移到类上:

这三个类都可能有属性,都可能有方法,都可能有构造方法。不管有没有,只要他们有的话,肯定长得很像。

那有没有一种方法可以去描述这个类模板?类是描述对象的,那有没有什么是描述类的呢?

这就是所谓的反射技术

Class类型的类模板,可以描述类模板。这个关系不就是类和对象的关系吗,只不过是上升了一层。

引出第二句话:

类可以通过反射拿到Class类型的类模板

这里记住Class类型的类模板肚子里的三个东西,是下面反射时访问成员方法、变量、构造方法的关键字:

1.Field

2.Method

3.Constructor

0x03 小结

因此可以通过这个Class类型的类模板得到任意一个class对象,也就可以得到任意一个对象了。

那么反射就是反过来,你可以通过对象去反射得到他的类,又可以通过类反射得到Class类型的类模板,因此P神的文章有这么一句话:

对象可以通过反射获取他的类,类可以通过反射拿到所有 ⽅法(包括私有),拿到的⽅法可以调⽤,总之通过“反射”,我们可以将Java这种静态语⾔附加上动态 特性。 –P神

0x04操作语法:

获取运行时类Class:

Class clazz = 类名.class;

Class clazz = 对象应用.getClass();

Class clazz = Class.forName(“包名.类名”);

1.分析类的结构(修饰符,类名,继承的父类,实现的父接口)

2.反射创建对象

类中默认无参数构造方法,执行创建 newInstance();

找到某一个构造方法,执行创建

3.获取类中的成员

3.1获取属性 clazz.getField(“属性名”);

分析属性的结构(修饰符,属性类型,属性名,注解)

存取信息 set()/get()

3.2获取方法 clazz.getMethod(“方法名”,参数类型….);

分析方法的结构(修饰符,返回类型,方法名,参数类型,异常类型,注解)

执行方法 invoke()

3.3获取构造方法 clazz.getConstructor(参数类型…);

分析构造方法的结构(修饰符,参数类型,异常类型,注解)

执行构造方法 newInstance();

0x05实操加深理解

1.新建一个包,随便命名,我这里命名为:MyselfTest

2.在MyselfTest包下创建一个学生类:

package MyselfTest;

public class Student {
int age;
    Student() {
        System.out.println("默认无参构造方法");
    }
/*
    * 有加public和没加public的区别:
    *   1.加了public代表赋予公共权限
    *   2.没加默认是private
    *   3.同一个包下面可以任意调用,不在乎调用权限
    * */
public Student(String name){
        System.out.println("执行了name的构造方法");
    }

private Student(int age){
        System.out.println("执行了age的私有构造方法");
    }
}

3.获取类名

package MyselfTest;

public class reflect {
public static void main(String[] args){
Student stu1 = new Student();

/*
        * 获取类名三方式
        * Class clazz = 类名.class;
        * Class clazz = 对象应用.getClass();
        * Class clazz = Class.forName("包名.类名");
        * */

//方法一
Class stu1Class = Student.class;
        System.out.println(stu1Class);

//方法二
Class stu2Class = stu1.getClass();
        System.out.println(stu2Class);

//方法三
try {
Class stu3Class = Class.forName("MyselfTest.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
            System.out.println(stu3Class);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

默认无参构造方法

class MyselfTest.Student

class MyselfTest.Student

class MyselfTest.Student

这里方法三最常用,能记住就记住!

这里方法三最常用,能记住就记住!

这里方法三最常用,能记住就记住!

4.调用构造方法

4.1.获取构造方法:

clazz.getConstructors(); 获取所有构造方法

如果想要获取私有需要加上Declared,即:

getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

这里getConstructor(Class… parameterTypes)得到的是Constructor类型的多个方法,需要用Constructor类型的数组变量去接:(示例)

Constructor[] con = clazz.getConstructor(int.class);

4.2.获取单个构造方法并调用

getConstructor(Class… parameterTypes):获取单个的”公有的”构造方法:

getDeclaredConstructor(Class… parameterTypes):获取”某个构造方法”可以是私有的,或受保护、默认、公有;

这里getConstructor(Class… parameterTypes)得到的是Constructor类型的单个方法,需要用Constructor类型的变量去接:(示例)

Constructor con = clazz.getConstructor(int.class);

4.3.newInstance

通过 Class 类(new类的不行)的 newInstance() 方法创建对象,该方法要求该 Class 对应类有无参构造方法

执行 newInstance()方法实际上就是使用对应类的无参构造方法来创建该类的实例

其代码的作用等价于Super sup = new Super()

如果newlnstance执行不成功,有两个原因:

1.该类没有无参数的构造方法

2.使用的类构造函数是私有的

4.4. invoke

invoke的普通用法:

Class<?> clazz3 = Class.forName("MyselfTest.Student");
clazz3.getMethod("function").invoke(clazz3);

关于invoke这里还有一个注意点:

invoke 的作用是执行方法,它的第一个参数是:

  1. 如果这个方法是一个普通方法,那么第一个参数是类对象
  2. 如果这个方法是一个静态方法,那么第一个参数是类

invoke用于调用方法,p神这里是这样调用的:

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

//这里有个让我很疑惑的点,这里invoke是怎么成功调用方法的

正常来说应该是这样:

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

但是会报错:

原因是 Runtime 类的构造方法是私有的。 有同学就比较好奇,为什么会有类的构造方法是私有的,难道他不想让用户使用这个类吗?这其实涉及 到很常见的设计模式:“单例模式”。(有时候工厂模式也会写成类似) 比如,对于Web应用来说,数据库连接只需要建立一次,而不是每次用到数据库的时候再新建立一个连 接,此时作为开发者你就可以将数据库连接使用的类的构造函数设置为私有,然后编写一个静态方法来 获取

给了一个连接数据库的例子:

public class TrainDB {
private static TrainDB instance = new TrainDB();

public static TrainDB getInstance() {
return instance;
            }

private TrainDB() {
// 建立连接的代码...
            }
        }

/*
上面payload的中invoke后面的内容:
    invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe")
这其中的clazz.getMethod("getRuntime").invoke(clazz)就是获取形如instance的东西
*/

于是有了这个payload:

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


//getMethod还没有讲,下面再提

分解开来就是这样:

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

4.5.获取成员构造方法并调用

​

结果:

5.获取属性

clazz1.getFields();

/*
Student类里面的:
public class Student {
    private int age;
    public String name; // 变量也可以设置其访问类型
    public char school;
    char sex;
    private String phoneNum;
}
*/
package MyselfTest;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class getMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
Class clazz1 = Class.forName("MyselfTest.Student");
        Field[] publicPart1 = clazz1.getFields();
for(Field p : publicPart1){
            System.out.println("多个方法:(public)"+p);
        }

Class clazz2 = Class.forName("MyselfTest.Student");
        Field[] publicPart2 = clazz2.getDeclaredFields();
for(Field p:publicPart2){
            System.out.println("多个方法:(all)"+p);
        }

Class clazz3 = Class.forName("MyselfTest.Student");
Field publicPart3 = clazz3.getField("school");
        System.out.println("单个方法:"+publicPart3);

Class clazz4 = Class.forName("MyselfTest.Student");
Field publicPart4 = clazz4.getDeclaredField("phoneNum");
        System.out.println("单个私有:"+publicPart4);
    }
}

结果:

6.获取方法

/*
使用到的方法:
public void function(){
        System.out.println("public void function");
    }
    static void function1(){
        System.out.println("static void function");
    }
    public void function2(int age){
        System.out.println("(void public)Age:"+age);
    }
    static void function3(String school){
        System.out.println("(private void)School:"+school);
    }
*/
package MyselfTest;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class getMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException {
        Class<?> clazz1 = Class.forName("MyselfTest.Student");
Method method1 = clazz1.getMethod("function");
//function()是public
        method1.invoke(clazz1.newInstance());// 这里一定要newInstance()

        Class<?> clazz2 = Class.forName("MyselfTest.Student");
Method method2 = clazz2.getDeclaredMethod("function1");
//function1()是static
        method2.invoke(clazz2);

        Class<?> clazz3 = Class.forName("MyselfTest.Student");
Method method3 = clazz3.getMethod("function2",int.class);
        method3.invoke(clazz3.newInstance(),18);

        Class<?> clazz4 = Class.forName("MyselfTest.Student");
Method method4 = clazz4.getDeclaredMethod("function3",String.class);
        method4.invoke(clazz4.newInstance(),"FJPSC");
    }
}

结果:

参考链接

Java基础之—反射(非常重要)_敬业小码哥的博客-CSDN博客_java反射

newInstance() 方法_LiLiYuan.的博客-CSDN博客_newinstance

Java安全漫谈 - 『代码审计』知识星球