Java安全-1.ClassLoader

ClassLoader

JAVA类

Java语言无法被操作系统直接识别,需要经过JVM将.java文件编译为.class文件(编译),后续JVM在执行程序时便首先解析class文件的二进制内容,执行.class文件的字节码(解释)

ClassLoader加载流程

类加载器

  1. Bootstrap ClassLoader (引导类加载器),最顶层加载器,主要加载核心类库,由 C++ 实现, 负责加载 %JAVA_HOME%lib 目录中的 java 核心类库, 路径也可由 -Xbootclasspath 参数指定
  2. Extention ClassLoader(扩展类加载器),由 sun.misc.Launcher$ExtClassLoader 实现, 负责加载 %JAVA_HOMElibext 目录中的 java 扩展库, 路径也可由 -Djava.ext.dirs 参数指定
  3. Appclass Loader(系统类加载器,SystemAppClass),默认加载器,在不指定的情况下,默认使用Appclass Loader由 sun.misc.Launcher$AppClassLoader 实现, 负责加载当前 classpath 下的 class 文件, 路径也可由 -Djava.class.path 参数指定
  4. UserDefineClassLoader: 为开发者自行编写, 通过继承 java.lang.ClassLoader 并重写相关方法来自定义 ClassLoader

ClassLoader类具有的核心方法有:

  1. loadclass:加载指定的Java类
  2. findclass:查找指定的java类
  3. findLoadedClass:查找JVM已经加载过的类
  4. defineClass:定义一个Java类
  5. resolveClass:链接指定的Java类

加载器加载顺序为:

  1. Bootstrap ClassLoader
  2. Extention ClassLoader
  3. Appclass Loader

加载顺序流程(双亲委派机制)如下

ClassLoader加载com.anbai.sec.classloader.TestHelloWorldloadClass重要流程如下:

  1. ClassLoader会调用public Class<?> loadClass(String name)方法加载com.anbai.sec.classloader.TestHelloWorld类。
  2. 调用findLoadedClass方法检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
  3. 如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestHelloWorld类,否则使用JVM的Bootstrap ClassLoader加载。
  4. 如果上一步无法加载TestHelloWorld类,那么调用自身的findClass方法尝试加载TestHelloWorld类。
  5. 如果当前的ClassLoader没有重写了findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的com.anbai.sec.classloader.TestHelloWorld类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
  6. 如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。
  7. 返回一个被JVM加载后的java.lang.Class类对象。

其中,三四步属于双亲委托机制,优先使用父类加载器加载

不同的ClassLoader会有父子关系,而非继承关系,本质是Java.lang.ClassLoader内部定义了指向父加载器的常量parent,可以通过方法getParent()方法获取父加载器

ClassLoader cl = Test.class.getClassLoader();
System.out.println("cl's parent is " + cl.getParent().toString());

eg:AppClass Loader的父类加载器为Extention ClassLoader

但是,再次获取Extention ClassLoader的父类加载器会发生一个空指针报错

(参考x1r0z大佬博客):Extention ClassLoader的父类加载器BoostrapClassLoader 是由 C++ 实现, 无法在 Java 中获取对应的引用, 所以显示 null

image-20240317135328040

双亲委派机制

当前 ClassLoader 在加载 class 时, 会将被加载的 class 委托给它的父加载器加载, 以此类推直到最顶层的 BootstrapClassLoader, 如果 BootstrapClassLoader 无法加载这个 class, 则会抛出异常, 然后被子加载器捕获, 并由子加载器继续尝试加载, 如果仍然无法加载, 就一层一层往下直到最开始的 ClassLoader, 如果这个 ClassLoader 也无法加载对应 class, 最终则会抛出 java.lang.ClassNotFoundException 异常

image-20240318195146213

双亲委派的机制的优势在于:

  • 避免类的重复加载
  • 保护程序安全,防止核心API被随意篡改
    • 自定义类:java.lang.String
    • 自定义类:java.lang.ShkStart(报错:阻止创建 java.lang开头的类)

打破双亲委派机制

实际项目例如:JDBC、JNDI、Tomcat等都打破了双亲委派机制

仅仅要靠 JVM 中的根加载器是无法加载进入 JVM 运行的,所以这时候我们就要破坏双亲委派机制,实现框架内的类由框架内的加载器加载。

自定义一个 ClassLoader, 并且重写一个 loadclass 即可打破双亲委派机制

自定义ClassLoader

Hello类:

public class Hello {
    public Hello(){
        try{
            Runtime.getRuntime().exec("calc.exe");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

自定义ClassLoader 加载Hello类

FileClassLoader:

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileClassLoader extends ClassLoader{
    protected String basePath;
    public FileClassLoader(String basePath){
        super();
        this.basePath=basePath;
    }
    @Override
    protected Class<?> findClass(String name){
        byte[] arr;
        try{
            Path path = Paths.get(this.basePath, name+".class");
            arr = Files.readAllBytes(path);
            return defineClass(name,arr,0,arr.length);
        }catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
    public static void main(String[] args)throws Exception{
        ClassLoader classLoader = new FileClassLoader("D:\");
        Class clazz = classLoader.loadClass("Hello");
        clazz.newInstance();
    }
}

image-20240318202024452

URLClassLoader

继承自java.lang.ClassLoader,提供了加载远程jar包的能力,可以在漏洞利用时利用该方法来加载远程的jar实现远程的类方法代用

将上一步写的Hello.java文件编译出的Hello.class文件放在网站根目录下

编写远程加载payload:

import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
public class URLLoader {
    public static void main(String[] args)throws Exception{
        URL url = new URL("http://localhost");
        URLClassLoader loader = new URLClassLoader(new URL[]{url});
        Class clazz = loader.loadClass("Hello");
        clazz.newInstance();
    }
}

image-20240318203556668

image-20240318203049121

类加载隔离

创建类加载器的时候可以指定该类加载的父类加载器,ClassLoader是有隔离机制的,不同的ClassLoader可以加载相同的Class(两者必须是非继承关系),同级ClassLoader跨类加载器调用方法时必须使用反射。

跨类加载器加载

RASP和IAST经常会用到跨类加载器加载类的情况,因为RASP/IAST会在任意可能存在安全风险的类中插入检测代码,因此必须得保证RASP/IAST的类能够被插入的类所使用的类加载正确加载,否则就会出现ClassNotFoundException,除此之外,跨类加载器调用类方法时需要特别注意一个基本原则:ClassLoader A和ClassLoader B可以加载相同类名的类,但是ClassLoader A中的Class A和ClassLoader B中的Class A是完全不同的对象,两者之间调用只能通过反射

BCEL ClassLoader

BCEL(Apache Commons BCEL™)是一个用于分析、创建和操纵Java类文件的工具库,Oracle JDK引用了BCEL库,不过修改了原包名org.apache.bcel.util.ClassLoadercom.sun.org.apache.bcel.internal.util.ClassLoader,BCEL的类加载器在解析类名时会对ClassName中有$$BCEL$$标识的类做特殊处理,该特性经常被用于编写各类攻击Payload。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇