Java代码审计前置知识——Java 反射机制

目录

前言:

(一)什么是反射

Oracle 官方解释

(二)反射的用途

(三)反射的基本运用

3.1.  取类对象

(1)使用 forName()方法

(2)直接获取

 (3)使用 getClass() 方法

(4)使用 getSystemClassLoader().loadClass() 方法

3.2.获取类方法

(1)getDeclaredMethods 方法

 (2)getMethods 方法

 (3)getMethod 方法

(4)getDeclaredMethod 方法

3.3.  获取类成员变量

(1)getDeclaredFields 方法

 (2)getFields 方法

(3)getDeclaredField 方法

 (4)getField 方法

(四)不安全的反射


前言:

Java 反射机制可以无视类方法、变量去访问权限修饰符(如 protected private
等),并且可以调用任何类的任意方法、访问并修改成员变量值。换而言之,在能够
控制反射的类名、方法名和参数的前提下,如果我们发现一处 Java 反射调用漏洞,
则攻击者几乎可以为所欲为。

(一)什么是反射


反射( Reflection )是 Java 的特征之一。 C/C++ 语言中不存在反射,反射的存在
使运行中的 Java 程序能够获取自身的信息,并且可以操作类或对象的内部属性。那
么什么是反射呢?

Oracle 官方解释


Reflection enables Java code to discover information about the fields, methods and 
constructors of loaded classes, and to use reflected fields, methods, and constructors to 
operate on their underlying counterparts, within security restrictions.
简单来说,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。同样,Java 的反射机制也是如此,在运行状态中,通过 Java 的反射机制,我们能够判断一个对象所属的类;了解任意一个类的所有属性和方法;能够调用任意一个对象的任意方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射制。

(二)反射的用途


反射的用途很广泛。在开发过程中使用 Eclipse IDEA 等开发工具时,当我们输入一个对象或类并想调用它的属性或方法时,编译器会自动列出它的属性或方法,这是通过反射实现的;再如,JavaBean JSP 之间的调用也是通过反射实现的。反射最重要的用途是开发各种通用框架,如上文中提到的 Spring 框架以及 SpringMVC   框架,都是通过反射机制来实现的。

(三)反射的基本运用


由于大部分 Java 的应用框架采用了反射机制,因此掌握 Java 反射机制可以提高我们的代码审计能力。

3.1.  取类对象


1)使用 forName()方法


如果要使用 Class 类中的方法获取类对象,就需要使用 forName() 方法,只要有
类名称即可,使用更为方便,扩展性更强。图 1  所示为获取类对象的示例。
Java代码审计前置知识——Java 反射机制_第1张图片 图一  使用 forName() 方法获取类对象

 这种方法并不陌生,在配置 JDBC 的时候,我们通常采用这种方法,如图二

Java代码审计前置知识——Java 反射机制_第2张图片 图二 配置 JDBC

2)直接获取


任何数据类型都具备静态的属性,因此可以使用 .class 直接获取其对应的 Class
对象。这种方法相对简单,但要明确用到类中的静态成员,如图三
Java代码审计前置知识——Java 反射机制_第3张图片 图三  直接获取类对象

 3)使用 getClass() 方法


我们可以通过 Object 类中的 getClass() 方法来获取字节码对象。不过这种方法
较为烦琐,必须要明确具体的类,然后创建对象,如图四
Java代码审计前置知识——Java 反射机制_第4张图片 图四  使用 getClass() 方法获取类对象

4)使用 getSystemClassLoader().loadClass() 方法


getSystemClassLoader().loadClass() 方法与 forName() 方法类似,只要有类名称即可,但是与 forName() 方法有些区别。 forName() 的静态方法 JVM 会装载类,并且执行 static() 中的代码;而 getSystemClassLoader().loadClass() 不会执行 static() 中的代码。如上文中提到的使用 JDBC ,就是利用 forName() 方法,使 JVM 查找并加载指定的类到内存中,此时将“com.mysql.jdbc.Driver ”当作参数传入,就是告知 JVM去“com.mysql.jdbc ”路径下查找 Driver 类,并将其加载到内存中。具体方法如图五所示。
Java代码审计前置知识——Java 反射机制_第5张图片 图五  使用 getSystemClassLoader().loadClass() 方法获取类对象

3.2.获取类方法


1getDeclaredMethods 方法

getDeclaredMethods 方法返回类或接口声明的所有方法,包括 public protected、 private 和默认方法,但不包括继承的方法,具体方式如图 4-18 所示。

Java代码审计前置知识——Java 反射机制_第6张图片

 2getMethods 方法


getMethods 方法返回某个类的所有 public 方法,包括其继承类的 public 方法,具体方式如图 4-19 所示

Java代码审计前置知识——Java 反射机制_第7张图片

 3getMethod 方法


getMethod 方法只能返回一个特定的方法,如 Runtime 类中的 exec() 方法,该方法的第一个参数为方法名称,后面的参数为方法的参数对应 Class 的对象,具体方式如图 4-20 所示

Java代码审计前置知识——Java 反射机制_第8张图片

4getDeclaredMethod 方法


getDeclaredMethod 方法与 getMethod 类似,也只能返回一个特定的方法,该方法的第一个参数为方法名,第二个参数名是方法参数,具体方式如图 4-21 所示。

Java代码审计前置知识——Java 反射机制_第9张图片

3.3.  获取类成员变量


为了更直观地体现出获取类成员变量的方法,我们首先创建一个 Student 类,如图 4-22 所示。

Java代码审计前置知识——Java 反射机制_第10张图片

 要获取 Student 类成员变量,主要有以下几个方法。

1getDeclaredFields 方法


getDeclaredFields 方法能够获得类的成员变量数组,包括 public private 和proteced,但是不包括父类的声明字段。具体方式如图 4-23 所示
Java代码审计前置知识——Java 反射机制_第11张图片 图 4-23 getDeclaredFields 方法

 2getFields 方法


getFields 能够获得某个类的所有的 public 字段,包括父类中的字段,具体方式如图 4-24 所示
Java代码审计前置知识——Java 反射机制_第12张图片 图 4-24 getFields 方法

3getDeclaredField 方法


该方法与 getDeclaredFields 的区别是只能获得类的单个成员变量,这里我们仅想获得 Student 类中的 name 变量,具体方式如图 4-25 所示。
Java代码审计前置知识——Java 反射机制_第13张图片 图 4-25 getDeclaredField 方法

 4getField 方法


getFields 类似, getField 方法能够获得某个类特定的 public 字段,包括父类中的字段,这里想获得 Student 类中的 public 类型变量 content ,具体方式如图 4-26 所示
Java代码审计前置知识——Java 反射机制_第14张图片 图 4-26 getField 方法

(四)不安全的反射


如前所述,利用 Java 的反射机制,我们可以无视类方法、变量访问权限修饰符,调用任何类的任意方法、访问并修改成员变量值,但是这样做可能导致安全问题。如果一个攻击者能够通过应用程序创建意外的控制流路径,就有可能绕过安全检查发起相关攻击。假设有一段代码如下
String name = request.getParameter("name"); 
 Command command = null; 
 if (name.equals("Delect")) { 
 command = new DelectCommand(); 
 } else if (ctl.equals("Add")) { 
 command = new AddCommand(); 
 } else { 
 ... 

 } 
 command.doAction(request);
其中存在一个字段 name ,当获取用户请求的 name 字段后进行判断时,如果请求的是 Delect 操作,则执行 DelectCommand 函数;如果执行的是 Add 操作,执行AddCommand 函数;如果不是这两种操作,则执行其他代码。
假如有开发者看到了这段代码,他认为可以使用 Java 的反射来重构此代码以减少代码行,如下所示:(log4j漏洞最后也是调用了newInstance)
String name = request.getParameter("name"); 
Class ComandClass = Class.forName(name + "Command"); 
Command command = (Command) CommandClass.newInstance(); 
command.doAction(request);
这样的重构看起来使代码行减少,消除了 if/else 块,而且可以在不修改命令分派器的情况下添加新的命令类型,但是如果没有对传入的 name 字段进行限制,就会实例化Command 接口的任何对象,从而导致安全问题。
实际上,攻击者甚至不局限于本例中的 Command 接口对象,而是使用任何其他对象来实现,如调用系统中任何对象的默认构造函数,或者调用 Runtime 对象去执行系统命令,这可能导致远程命令执行出现漏洞,因此不安全的反射的危害性极大,也是我们审计过程中需要重点关注的内容。

你可能感兴趣的:(Javaee,java,开发语言,web安全)