反射
反射是一种强大的机制,它允许程序在运行时访问、检查和修改它自己的结构,比如类、接口、字段(属性)和方法。反射提供了一种动态性,使得Java程序可以在运行时处理对象和类。所以说通过反射,我们可以使java这类静态语言附上动态的特征。
几个反射中重要的方法:
获取类的⽅法: forName
实例化类对象的⽅法: newInstance
获取函数的⽅法: getMethod
执⾏函数的⽅法: invoke
这⼏个⽅法基本上包揽了Java安全⾥各种和反射有关的Payload。
获取类名方法:
obj.class //直接通过类名获取,已加载某个类
obj.getClass() //通过实例对象获取,存在某个类的实例obj
obj.forName() //通过类名的字符串形式获取,知道类名,获取类
forName重载形式:
-
Class.forName(String className)
:
这是最基本的重载形式,它接受一个类名的字符串作为参数,并返回对应的Class
对象。Class<?> clazz = Class.forName("java.lang.String");
-
Class.forName(String className, boolean initialize, ClassLoader loader)
:
这个重载形式允许你指定是否要初始化类(即调用类的静态初始化块和静态变量赋值),以及使用哪个类加载器来加载类。initialize
参数为true
时,类会被初始化;为false
时,类不会被初始化,这在Java 9及以后的版本中被称为“未链接”状态。loader
参数指定了用于加载类的ClassLoader
实例。如果为null
,则使用调用者的类加载器。ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Class<?> clazz = Class.forName("java.lang.String", true, classLoader);
在Java中,当你使用
Class.forName()
方法加载类时,无论initialize
参数是true
还是false
,JVM都会执行类的静态初始化块(即static
块)。这是因为Class.forName()
方法的目的就是加载并初始化类,包括执行静态初始化块中的代码。所以forname执行的是静态初始化块。静态初始化块在类被Java虚拟机(JVM)加载并初始化时执行。这发生在类首次被“主动使用”时,静态初始化块只执行一次,无论类的构造函数被调用多少次。实例初始化块在类的每个实例被创建时执行,实例初始化块在构造函数之前执行。构造函数在创建类的新实例时调用,构造函数在实例初始化块之后执行。但在执行实例化初始化块前会先执行构造函数的super()。
另外:
java我们一般情况下拿到一个类需要用import导入才可以,不过forName就不需要,那么对我们攻击者来说很有利,可以加载任意类。
一些类名中会使用 符号, 符号, 符号,作用是查找内部类。
Java的普通类 C1中支持编写内部类 C2,而在编译的时候,会生成两个文件:C1.class 和 C1 C 2. c l a s s ,我们可以把他们看作两个无关的类,通过 C l a s s . f o r N a m e ( " C 1 C2.class,我们可以把他们看作两个无关的类,通过Class.forName("C1 C2.class,我们可以把他们看作两个无关的类,通过Class.forName("C1C2") 即可加载这个内部类。
在Java中,内部类是一种定义在另一个类中的类。内部类可以访问外部类的成员,包括私有成员。当内部类被编译时,它们会被编译成独立的.class
文件,但文件名会包含外部类的名称和一个$
符号,后跟内部类的名称。当你使用Class.forName("C1$C2")
来加载内部类时,JVM会查找名为C1$C2
的类,并将其加载到内存中。如果C1$C2
类依赖于外部类C1
的某些特性,那么C1
类也必须已经被加载,因为内部类与外部类之间存在依赖关系。获得类以后,我们可以继续使用反射来获取这个类中的属性、方法,也可以实例化这个类,并调用方法。
获取类的字段:
obj.getFields() //获取所有public字段
obj.getDeclaredFields() //获取所有字段(包含private)
获取类的方法:
obj.getMethods() //获取所有public方法
obj.getDeclaredMethods() //获取所有方法(包含private)
Java中支持类的重载,不能仅通过函数名来确定一个函数,还需要传给他你想要获取的函数的参数类型列表。
获取类的构造器:
obj.getConstructors() //获取所有public构造器
obj.getDeclaredConstructors() //获取所有构造器,私有记得用法setAccessible修改作用域
创建类实例:
obj.newInstance() //只适用于无参构造器
constructor = obj.getDeclaredConstructor()
constructor.newInstance() //有参构造器
class.newInstance()的作用就是调用这个类的无参构造函数,不过,我们有时候在写漏洞利用方法的时候,会发现使用newInstance总是不成功,这时候原因可能是:
-
使用的类没有无参构造函数
-
使用的类构造函数是私有的
这是因为私有类是限定在定义它的类内部使用的,它不能被外部类直接访问,因此也不能通过反射机制来创建实例。在Java 9及以后的版本中,引入了一个新的方法getDeclaredConstructor()
,它可以用来获取私有构造器,但是即使如此,你仍然需要在定义私有类的类内部来使用它。Constructor<InnerClass> constructor = InnerClass.class.getDeclaredConstructor(); InnerClass instance = constructor.newInstance();
但是,这仍然需要
InnerClass
不是顶级私有类,并且你需要有权限访问它。对于顶级私有类,你仍然不能通过反射来创建实例。这里有一个特殊的类(java.lang.Runtime),这里是很常见的单例模式(单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式通常用于管理共享资源,如配置信息、线程池、缓存等。)。
java.lang.Runtime
是 Java 的一个核心类,它提供了一系列方法来与运行时环境进行交互。这个类不能被实例化,但可以通过Runtime.getRuntime()
方法获取其实例,该实例代表了当前 Java 应用程序的运行时环境。
Runtime
类的一些常用方法:
Process exec(String command)
:执行指定的字符串命令,并返回一个Process
对象。
void load(String filename)
:从指定的文件路径加载库(如DLL或SO文件)。
void loadLibrary(String libname)
:从系统的库路径中加载库。
void addShutdownHook(Thread hook)
:注册一个新线程,该线程将在 Java 虚拟机退出时执行。
void removeShutdownHook(Thread hook)
:取消注册之前添加的shutdown hook。
获取注解:
obj.getAnnotations()
执行方法:
method.invoke()
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数是类
另一种执行命令方式:ProcessBuilder。使用反射来获取其类构造器,然后调用start()来执行命令。
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
有时候我们利用漏洞的时候(在表 达式上下文中)是没有强转换类型的。所以,我们仍需利用反射来完成这一步。
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
通过getMethod(“start”)获取到start方法,然后 invoke 执行,根据咱们上面说的invoke 的第一个参数就是 ProcessBuilder Object了。
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
在调用newInstance的时候,因为这个函数本身接收的是一个可变长参数,我们传给ProcessBuilder的也是一个可变长参数,二者叠加为一个二维数组。
标签:反射,forName,Java,安全,获取,实例,obj,Class From: https://blog.csdn.net/wbqww_/article/details/143168556