• Java代码审计-Java的反射机制


    1、反射的定义和用途

    程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态的获取信息以及动态调用对象的方法的功能称为 java 的反射机制。

    2、反射的基本运用

    • java.lang.Runtime类 主要方法是getRuntime(),Runtime可以调用exec()方法执行命令;

      ​ 每个java程序中都有一个Runtime实例,这个Runtime实例调用getRuntime方法,返回Runtime对象,这个对象拥有exec执行命令的方法

    • 获取类的对象 forName() 直接获取 getClass getSystemClassLoader.loadClass()方法

    • 构造任意类的对象 无参数的时候使用 className.newInstance() 有参数的时候使用 getConstructor.newInstance()

    • 调用任意实例对象的方法: invoke()

    3、不安全的反射

    一段利用反射构建的具有动态特性的代码

    public void execute(String className, String methodName) throws Exception {
     Class clazz = Class.forName(className);
     clazz.getMethod(methodName).invoke(clazz.newInstance());
    }
    
    • 1
    • 2
    • 3
    • 4

    forName 有两个函数重载:

    • Class forName(String name)
    • Class forName(String name, boolean initialize, ClassLoader loader)
    Class.forName(className)
    // 等于
    Class.forName(className, true, currentLoader)
    
    • 1
    • 2
    • 3

    关键是对 initialize的理解

    明确一点类初始化对象初始化是不同的概念,Class的获取一般与目标类的初始化有关,和目标对象初始化无关

    initialize对应的是类的初始化

    4、单例模式

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

    4.1什么是单例模式

    Runtime类就是单例模式,意思是只有类初始化的时候会执行一次构造函数,后面只能通过getInstance()方法 (Runtime里面就是getRuntime)获取这个对象

    4.2 为什么 提到单例模式

    //这段代码运行会报错,cannot access a member of class java.lang.Runtime (in module java.base) with modifiers "private",原因就是构造方法是私有的,不能访问
    Class cls = Class.forName("java.lang.Runtime");
            cls.getMethod("exec",String.class).invoke(cls.newInstance(),"calc.exe");
    
    
    • 1
    • 2
    • 3
    • 4
    • getMethod()方法返回一个类的某个特定的公有方法,需要通过方法名传入的参数来确定某个具体的方法,这里与函数的重载相关;函数的重载就是相同的函数名,但是接受的参数个数和参数类型可以不同,这就是函数的重载

    • invoke()方法 的作用是执行方法,getMethod()返回的方法想要执行,通过调用invoke方法来实现。普通的方法调用模式 [1].method([2],[3],...),用invoke表现为 method.invoke([1],[2],[3],....),其中这个[1]就是实例化的对象

    前面提到 Runtime类只在初始化的时候执行构造函数得到实例化的对象,后面要想在得到 Runtime 的实例化对象不能通过 newInstance()的方法得到,而是要通过 getRuntime()的方法得到一个Runtime的实例化对象,然后调用exec方法

    5、getConstrutor 与 变长参数构造

    如果一个类中没有无参构造方法,或者没有单例模式的静态方法,那么需要通过新的构造方法 getConstructor来做,因为构造方法也有可能重载,所以需要传递参数类型给 getConstructor,这个参数的类型根据构造方法的参数类型确定

    ProcessBuilder有两个构造函数:

    • public ProcessBuilder(List command)
    • public ProcessBuilder(String… command)

    根据这两种不同的构造方法,构造出两种类型的反射语句

    第一种

    Class clazz = Class.forName("java.lang.ProcessBuilder");
    
    clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
    
    • 1
    • 2
    • 3

    第二种

    Class clazz = Class.forName("java.lang.ProcessBuilder");
    
    clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
            
    
    • 1
    • 2
    • 3
    • 4

    第二种涉及到变长参数,可以看到第二种的代码里面有一个二维数组,这是因为newInstance()ProcessBuilder()都是接受变长参数,两个数组相加得到二维数组

    利用完全反射的方式编写代码,所有操作通过Class对象和Method对象实现

    Class clazz = Class.forName("java.lang.ProcessBuilder");
    Method getConstructorMethod = clazz.getClass().getMethod("getConstructor",new Class[]{Class[].class});
    Object b = getConstructorMethod.invoke(clazz,new Object[]{new Class[]{String[].class}});
    Method newInstanceMethod = b.getClass().getMethod("newInstance",new Class[]{Object[].class});
    Object d = newInstanceMethod.invoke(b,new Object[]{new String[][]{{"calc.exe"}}});
    Method startMethod = d.getClass().getMethod("start",new Class[]{});
    startMethod.invoke(d,new Object[]{});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    《六月集训》(第二十四天)——线段树
    .NET 与Java 常见技术名词与抽象概念对照
    12、Kubernetes中KubeProxy实现之iptables和ipvs
    【大虾送书第八期】揭秘分布式文件系统大规模元数据管理机制——以Alluxio文件系统为例
    基于JavaWeb的物流管理系统的设计与实现
    ESP32上实现面向对象的C++ OOP——头文件、源文件、构造与析构函数
    SpringBoot(二)集成 Quartz:2.5.4
    集合的增删改查?--Python
    硬件时钟和系统时钟的同步机制及案例分享
    编辑器实现思路
  • 原文地址:https://blog.csdn.net/Little_jcak/article/details/125438305