java安全基础_反射篇
Java安全基础——反射篇
Java反射
什么是JAVA反射
Java反射机制是在运行状态时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。
简单的说,就是通过java反射机制,我们可以获取任意类的成员方法、变量这些,同时,可以创建JAVA类实例,调用任意类方法。
还有就是想说一下这里为什么称为反射,在JAVA中,我们可以这样理解:通过new一个对象而调用其中方法的,称为”正射”,而不通过new一个对象取得其中方法的,称为”反射”。
反射的动态机制
反射机制是java实现动态语言的关键,也就是通过反射机制来实现类动态加载
静态加载:编译时加载相关的类,没有就会报错
就比如,
1 |
|
如果 Dog
类没有定义,编译器会直接报错 “cannot find symbol: class Dog”,因为 Java 采用静态加载机制,所有使用的类必须在编译时就能找到。
1 |
|
Class.forName("Person")
是反射机制,属于动态加载,只有在运行时才会去查找 Person
类。
如果 Person
类不存在,编译时不会报错(因为反射不需要在编译时确定类),但运行时会抛出ClassNotFoundException。
反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量、构造器、成员方法等等),并能操作对象的属性及方法。加载完类之后,就会在堆中生成一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了类的完整结构信息。这里借用大佬的反射流程图
ClassLoader
就是⼀个“加载器”,告诉Java虚拟机如何加载这个类。
那么java的反射机制会完成:
- 运行时判断任意一个对象所属的类
使用 Object.getClass()
方法可以获取对象的 Class
对象,从而得知该对象所属的类。
1 |
|
- 运行时构造任意一个类的对象
使用Class.forName()
和newInstance()
可以创建类的对象
1 |
|
- 在运行时得到一个类的所具有的成员变量和方法
使用Class
类的方法getFields()
,getDeclaredFields()
,getMethods()
,getDeclaredMethods()
可以获取字段和方法信息
1 |
|
Class.forName
和Person.class
总结对比:
特性 | Class.forName("Person") |
Person.class |
---|---|---|
加载方式 | 运行时通过字符串动态加载 | 编译时已知 |
是否安全 | 不安全,可能类不存在会抛异常 | 安全,编译时检查 |
是否需要全限定名 | 是,必须包含包名 | 否 |
是否能用于泛型等 | 可以,但更适合反射用途 | 适合泛型、注解、静态反射等 |
用途 | 框架/工具/反射 | 正常开发、泛型等 |
- 在运行时调用任意一个对象的成员变量和方法
通过反射可以访问私有变量、调用私有或公有方法。
1 |
|
- 生成动态代理
Java 提供 Proxy
类用于创建动态代理对象,可以在运行时为某个接口生成代理,实现横切逻辑(如日志记录、权限校验等)。
1 |
|
反射相关的主要类如下:
1
2
3
4
1、Java.long.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
2、Java.lang.reflect.Method:代表类的方法
3、Java.lang.reflect.Field:代表类的成员变量
4、Java.lang.reflect.Constructor:代表类的构造方法
上面的每个步骤已经演示。
Class类的分析
Class类对象不是new出来的,而是系统创建的。
Class类是由loadClass()方法完成类加载,生成了某个类对应的Class类对象。
前提是你的类没有加载过,才会进入到loadClass()
方法,加载过的话,就直接返回了,就比如:
1 |
|
因为Object
类肯定加载过了,他是主类嘛
我这里使用的是java.lang.Runtime
打断点调就可以了
对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
下面即可证明
常用方法
字段获取和修改的方法
方法 | 描述 |
---|---|
getFields() | 获取类所有的字段,只能获取public类型的字段,可以获取父类的字段。 |
getDeclaredField(String name) | 根据字段名获取对应的字段,可以获取public、protected和private类型的字段,不能获取父类的字段。 |
getDeclaredFields() | 获取类所有的字段,包括public、protected和private。不能获取父类的字段。 |
getField(String name) | 根据字段名获取对应的字段,只能获取public类型的字段,可以获取父类的字段。 |
在进行私有属性获取时,有两个注意事项。第一,必须使用getDeclaredField()
函数来获取。第二,在对字段进行操作之前,必须field.setAccessible(true);
这个表示设置允许对字段进行操作(上面也已经演示过)。
方法 | 描述 |
---|---|
field.set(Obj,field_value) | 设置Obj对象的field字段值为field_value |
field.get(null) | 获取static修饰的静态字段field的值 |
field.set(null,field_value) | 设置static修饰的静态字段field的值为field_value |
field.get(Obj) | 获取Obj对象的field字段 |
方法获取和访问
方法 | 描述 |
---|---|
getMethods() | 通过反射获取一个类的某个特定的公有方法,只能获取public类型的字段,可以获取父类的方法。 |
getDeclaredMethod(String name, Class… parameterTypes) | 根据方法名获取对应的方法,可以获取public、protected和private类型的方法,不能获取父类的方法。 |
getDeclaredMethods() | 获取类所有的方法,包括public、protected和private。不能获取父类的方法。 |
getMethod(String name, Class… parameterTypes) | 根据方法名和参数类型获取对应的方法,只能获取public类型的方法,可以获取父类的方法。 |
invoke
——反射执行方法的函数
要使用通过反射获取的方法,需要使用invoke函数。关于invoke方法的使用如下:
方法 | 描述 |
---|---|
invoke(Object obj, Object[] args) | 执行通过反射获取的方法,如果这个方法是普通方法,obj代表要执行方法的类对象,如果是一个静态方法,那么obj是类,args代表方法的参数。 |
我们正常执行方法是[1].method([2],[3],[4]...)
,反射上就是method.invoke([1],[2],[3],[4]...)
。
构造函数获取和使用
方法 | 描述 |
---|---|
getConstructors() | 获取类所有的构造函数,只能获取public类型的字段,不能获取父类的构造函数。和 getMethod 类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载,所以必须用参数列表类型才能唯一确定一个构造函数。 |
getDeclaredConstructor (Class… parameterTypes) | 所以必须用参数列表类型才能唯一确定一个构造函数。根据参数类型获取对应的构造函数,可以获取public、protected和private类型的构造函数,不能获取父类的构造函数。 |
getDeclaredConstructors() | 获取类所有的构造函数,包括public、protected和private。不能获取父类的构造函数。 |
getConstructor(Class… parameterTypes) | 根据参数类型获取对应的构造函数,只能获取public类型的构造函数,不能获取父类的构造函数。 |
newInstance——实例化类对象的方法
在获取到构造函数之后,需要通过newInstance
函数来生成类对象。关于newInstance
的使用如下所示:
方法 | 使用 |
---|---|
newInstance(Object … initargs) | newInstance函数接受可变的参数个数,构造函数实际有几个传输,这里就传递几个参数值。newInstance返回的数据类型是Object。 |
class.newInstance()
的作用就是调用这个类的无参构造函数,这个比较好理解。不过,我们有时候
在写漏洞利用方法的时候,会发现使用 newInstance
总是不成功,这时候原因可能是:
你使用的类没有无参构造函数
你使用的类构造函数是私有的
forName()——获取类的方法
forName
有两个函数重载:
Class<?> forName(String name)
Class<?> forName(String name, \*\*boolean** initialize, ClassLoader loader)
forName
方法可以获取到类的Class对象。当然获取到Class对象的不止forName()
这一种方式可以获取这个Class对象,getClass()
和class
也可以
1 |
|
在正常情况下,如果我们想要拿到一个类,得import
才能调用,但是用forName
就不需要
就比如java.lang.Runtime
,我们就不能直接这样执行命令
1 |
|
因为Runtime
类的构造方法是私有的。也就是说,这个类是单例模式,里面有静态的执行命令的方法。我们只能通过Runtime.getRuntime()
来获取到Runtime
对象。
工厂模式(Factory Pattern)
定义:
工厂模式用于封装对象的创建过程。当你不想直接用
new
去创建对象时,而是通过一个“工厂”方法来完成对象的创建,这就是工厂模式。目的:
将创建对象的过程从使用者中分离出来,使得代码更易于维护、扩展和解耦。
单例模式(Singleton Pattern)
定义:
单例模式用于确保某一个类只有一个实例,并提供一个全局访问点来获取它。
目的:
控制实例的创建数量,通常用于管理共享资源,如数据库连接、配置管理器、线程池等。
特点:
- 只有一个实例
- 自行创建这个实例
- 提供一个访问它的静态方法
比较点 工厂模式 单例模式 目的 负责对象的“创建逻辑” 保证类的“唯一实例” 应用场景 创建多个相似对象,解耦客户端与对象创建 创建一个全局唯一的对象 实例数量 可以创建多个不同实例 只会有一个实例 是否涉及继承 通常涉及继承和接口实现 不一定
1 |
|
如果一个类没有无参构造方法,也没有单例的静态方法,我们就需要getConstructor
这个方法,拿到构造函数后使用newInstance
来执行,这里用ProcessBuilder
来执行命令进行演示。
ProcessBuilder
是 Java 中一个非常实用的类,用于创建并控制外部进程(系统命令)。也就是说,它可以让你在 Java 程序里调用像ping
、ls
、cmd
、python
等系统命令或者外部程序。
ProcessBuilder
是 Java 提供的一个类,位于java.lang
包下,用来启动和管理新的操作系统进程。
1
ProcessBuilder pb = new ProcessBuilder("命令", "参数1", "参数2", ...);
方法 说明 start()
启动新进程 command()
设置或获取命令及其参数 directory(File dir)
设置工作目录 redirectOutput()
将输出重定向到文件或父进程 redirectErrorStream(true)
将错误输出合并到标准输出
ProcessBuilder
有两个构造函数:
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
利用反射执行命令
1 |
|
那如果是一个方法或者构造方法是私有方法,使用getDeclared
便可以解决。
getDeclared 系列的反射了,与普通的 getMethod 、 getConstructor 区别是:
getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
方法 | 能访问谁? | 是否能访问私有成员? | 是否包含继承来的? |
---|---|---|---|
getMethod(String name, Class... parameterTypes) |
只能访问 public 方法(包括继承来的) | ❌ 否 | ✅ 是 |
getDeclaredMethod(String name, Class... parameterTypes) |
访问当前类中声明的所有方法(不管访问修饰符) | ✅ 是 | ❌ 否 |
方法 | 能访问谁? | 是否能访问私有构造器? | 是否包含父类的构造器? |
---|---|---|---|
getConstructor(...) |
只能访问 public 构造方法 | ❌ 否 | ✅ (但构造器本身不继承) |
getDeclaredConstructor(...) |
可访问当前类声明的任意构造方法(包括 private) | ✅ 是 | ❌ 否 |
AI给了个顺口溜,哈哈哈,挺有意思
getXxx()
:只管 public,啥都要“见得光”。
getDeclaredXxx()
:不管三七二十一,自己类里的全都能看。想用 private?你还得加
setAccessible(true)
。
我们拿Runtime
来举个例子,直接用getDeclaredConstructor
来获取私有的构造方法
1 |
|
总结一个
目的 | 方法 |
---|---|
获取 public 方法/构造器 | getMethod() / getConstructor() |
获取 private 或 protected | getDeclaredMethod() / getDeclaredConstructor() + setAccessible(true) |
调用静态方法 | invoke(null, ...) |
调用实例方法 | invoke(对象, ...) |
参考
java安全漫谈-01-03