自定义 ClassLoader 隔离运行不同版本jar包的方式

自定义 ClassLoader 隔离运行不同版本jar包的方式

💡 原文中文,约2800字,阅读约需7分钟。
📝

内容提要

文章介绍通过自定义ClassLoader隔离运行不同版本jar包的方法。将父加载器设为null,加载指定目录下的jar包,实现类加载隔离。示例代码展示StandardExecutorClassLoader实现,并应用于Shiro反序列化漏洞利用,加载不同版本的CommonsBeanutils库。

🔎

延伸解读

双亲委托与隔离的权衡

文章指出,要实现类加载隔离,必须将自定义ClassLoader的parent设为null,否则根据双亲委托模型,类加载请求会先委托给父加载器,导致无法加载不同版本的jar包。但仅设置parent为null还不够,因为findClass方法中在自定义加载器找不到类时,会回退到系统类加载器,这保证了基础类(如JDK类)仍能正常加载,但同时也意味着某些类可能被系统加载器加载,从而无法完全隔离。理解这一权衡有助于在实际应用中评估隔离的彻底性。

实际应用场景与限制

文章以Shiro反序列化漏洞利用为例,展示了如何通过自定义ClassLoader加载不同版本的CommonsBeanutils库。这种技术常用于安全研究或插件化系统,但需要注意:自定义ClassLoader加载的jar包必须是独立可运行的,且依赖关系需自行管理。此外,由于父加载器为null,自定义加载器无法自动加载JRE类,因此findClass中的回退机制至关重要,否则会抛出ClassNotFoundException。

代码实现要点

实现中,StandardExecutorClassLoader继承URLClassLoader,通过addURL方法动态添加指定目录下的jar包。loadResource方法会加载版本目录及其lib子目录下的所有jar包,这简化了依赖管理。但需注意,addURL方法使用file协议,且路径需为规范路径,否则可能因URL格式问题导致加载失败。此外,loadClass方法被重写,但仅调用super.loadClass,实际加载逻辑仍依赖URLClassLoader的默认行为。

Q&A

为什么自定义ClassLoader需要将父加载器设为null?

将父加载器设为null是为了实现类加载隔离。根据双亲委托模型,如果父加载器不为null,类加载请求会委托给父加载器,可能导致加载到启动项目的类,无法实现隔离。设为null后,自定义ClassLoader不会委托给App ClassLoader,从而保证加载的类与启动项目隔离。

StandardExecutorClassLoader是如何加载指定版本jar包的?

StandardExecutorClassLoader继承URLClassLoader,构造时接收版本号,根据版本号拼接路径(如lib/1.9.2),然后调用tryLoadJarInDir方法加载该目录及其子目录lib下的所有.jar文件,通过addURL方法将jar包添加到URLClassLoader的URL列表中。

在StandardExecutorClassLoader中,findClass方法的作用是什么?

findClass方法首先尝试从自定义ClassLoader的URL中查找类,如果找不到,则委托给StandardExecutorClassLoader.class.getClassLoader()(即启动项目的类加载器)加载,这样可以在隔离的同时,允许加载启动项目中的基础类。

如何利用自定义ClassLoader实现Shiro反序列化漏洞利用?

在Shiro反序列化漏洞利用中,通过创建StandardExecutorClassLoader实例并指定版本(如1.9.2),然后使用该ClassLoader加载特定版本的CommonsBeanutils库中的BeanComparator类,构造PriorityQueue对象,并设置属性为outputProperties,从而触发漏洞利用链。

自定义ClassLoader隔离运行不同版本jar包时,需要注意哪些问题?

需要注意:1. 父加载器必须设为null,否则无法实现隔离;2. 需要重写findClass方法,以便在自定义路径找不到类时回退到启动项目的类加载器;3. 需要正确加载指定目录下的jar包,包括子目录lib;4. 确保加载的jar包是经过二次开发的可独立运行项目。

文章中的StandardExecutorClassLoader是如何处理类加载失败的?

在loadClass方法中,直接调用super.loadClass(name),而findClass方法中,先尝试从自定义URL中查找,如果抛出ClassNotFoundException,则回退到StandardExecutorClassLoader.class.getClassLoader().loadClass(name),即使用启动项目的类加载器加载。

🏷️

标签

➡️

继续阅读