长亭百川云 - 文章详情

JDK 中的 checkPackageAccess

n1nty

58

2024-07-15

这是我学习 “Java 沙盒绕过” 过程中的一篇笔记。

checkPackageAccess 的作用

用于在 Java 沙盒开启的程序中,对受限访问包中的类做访问权限控制。

哪些包是受限访问包?

在 JDK 安装目录下的 jre/lib/security/java.security 文件中,有一份受限访问包的列表,这里截取部分内容:


package.access=sun.,\

               com.sun.xml.internal.,\

               com.sun.imageio.,\

               com.sun.istack.internal.,\

               com.sun.jmx.,\


上面的列表表示,在沙盒开启的情况下,非特权 Java 代码是无法访问 sun. 及列表中定义的其它包下面的类的,除非拥有对应的 RuntimePermission。

为什么这些包会被受限访问?

受限包中有大量 JDK 自身所依赖的类,这些类往往带有大量可影响安全的敏感功能。

JDK 中,有多个类拥有名为 checkPackageAccess 的方法,这里只记录三个最重要的。

SecurityManager.checkPackageAccess

这是最根本的进行受限包访问控制检查的地方,可以认为是权限检查的入口。下面要讲到的其它类中的 checkPackageAccess 方法最终都会调用 SecurityManager.checkPackageAccess 来进行访问控制。这里不记录它的细节,主要就是通过 AccessController.checkPermission 来进行权限检查。

ClassLoader.checkPackageAccess

ClassLoader.checkPackageAccess 是一个 private 方法,代码如下:

private void checkPackageAccess(Class<?> cls, ProtectionDomain pd) {  
    final SecurityManager sm = System.getSecurityManager();  
    if (sm != null) {  
        if (ReflectUtil.isNonPublicProxyClass(cls)) {  
            for (Class<?> intf: cls.getInterfaces()) {  
                checkPackageAccess(intf, pd);  
            }  
            return;  
        }  
  
        final String name = cls.getName();  
        final int i = name.lastIndexOf('.');  
        if (i != -1) {  
            AccessController.doPrivileged(new PrivilegedAction<Void>() {  
                public Void run() {  
                    sm.checkPackageAccess(name.substring(0, i));  
                    return null;                }  
            }, new AccessControlContext(new ProtectionDomain\[\] {pd}));  
        }  
    }  
    domains.add(pd);  
}

此方法基本没有任何的显式调用。它的主要作用是:在对类的依赖进行加载后,判断这些依赖是否是受限包中的类,以及当前代码是否有权限访问这些依赖。此方法会被 JVM 自动调用。

为了更清楚地解释,举一个例子:

ClassA 依赖于 ClassB。假如 ClassA 的类加载器是 AppClassLoader。在 AppClassLoader 加载了 ClassA 后,需要对 ClassA 进行 resolve,也就是加载 ClassA 的依赖。此时发现 ClassA 依赖于 ClassB ,便开始尝试加载 ClassB,但是 ClassB 最终由 AppClassLoader 的上层类加载器加载并定义。此时,出现了跨类加载器的依赖关系,在 ClassB 被加载完成后,它将会被返回给 AppClassLoader,同时 JVM 会自动调用 AppClassLoader 继承自 ClassLoader 的 checkPackageAccess,来检查当前代码是否有权限访问 ClassB(通过判断 ClassB 是否位于受限访问包中)。这样做的原因是为了防止恶意代码通过依赖的方式来访问受限包中的类。只有在出现跨类加载器的依赖关系时,此方法才会被 JVM 自动调用。如果 ClassB 最终也是由 AppClassLoader 加载的,则此方法不会被调用。

Class.checkPackageAccess

此方法主要被 Class.checkMemberAccess 调用。

Class.checkMemberAccess 主要被 Class 类的各种反射方法(如 getField/getMethod/getConstructor) 调用,来检查当前程序是否有权限来通过反射访问某个类的成员。

一个例子:

classA.getDeclaredMethod("xxx", ...)

在本文的讨论范围之内,此行代码会经过如下检查:

  1. checkMemberAccess,用于检查当前程序是否有权限利用反射来访问 classA 的类成员

  2. checkPackageAccess,用于检查当前程序是否有权限访问 classA(classA 是否是受限访问的)。这里有一个问题,能调用 classA.getDeclaredMethod 了,说明 classA 已经被加载到了,那是不是说明这里的 checkPackageAccess 肯定会成功?嘿嘿

类加载器与 checkPackageAccess

我曾经以为,任何类加载器在加载类的时候,肯定会自动进行受限检查,我认为这个逻辑应该是写死在 ClassLoader 这个所有类加载器的父类里面的。后来才发现,ClassLoader 里面只有那个上面讨论过的 checkPackageAccess 方法,而且这个方法只是在处理跨类加载器的依赖时才会被 JVM 自动调用。

那么真相是什么?

真相就是,类加载器在加载类时是否进行受限检查,完全是看类加载器自己是如何实现的。比如 ClassLoader 类的 默认的 loadClass 方法在加载类时,并不会进行检查。

Java 程序运行后,会有一个默认的类加载器链,如下:

  1. bootstrap class loader,C++ 实现,用于加载 rt.jar 中的 JDK 核心类,这些类会拥有所有特权

  2. ext class loader,Java 实现,用于加载 JDK 的扩展类,这些类会拥有所有特权

  3. app class loader,Java 实现

这三个类加载器中,只有 app class loader 在加载类时会进行受限检查,对应的代码如下:

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {  
    int var3 = var1.lastIndexOf(46);  
    if (var3 != -1) {  
        SecurityManager var4 = System.getSecurityManager();  
        if (var4 != null) {  
            var4.checkPackageAccess(var1.substring(0, var3));  
        }  
    }  
  
    return super.loadClass(var1, var2);  
}

注意 var4.checkPackageAccess 那一行。

同时我也注意到,直接 new 出来的 URLClassLoader 对象,在加载类时也是不进行受限检查的。

知道这些有什么用?

在有些时候,沙盒开启的情况下,是可以无视 checkPackageAccess 的。比如,你获取到了一个在 loadClass 的时候不进行 checkPackageAccess 的类加载器,如果它所有的上级加载器都不进行受限检查,那么即使沙盒处于开启的状态,你依然可以访问所有的受限类。

比如 loader1 就是这样的一个 class loader,那么可以直接使用

loader1.loadClass("sun.awt.SunTookkit") 

来访问受限类 sun.awt.SunToolkit。

但是如果你企图通过 loader1 来加载一个对 sun.awt.SunToolkit 有依赖的类,是会失败的,因为上面说过了,跨类加载器的依赖 JVM 会自动进行受限检查。

绕过了 checkPackageAccess 又有什么用呢?

在沙盒开启的情况下,仅仅是获取到了受限包中的类,是没有什么太大作用的。还需要能够访问这些类的敏感方法,才可以达到绕过沙盒的目的。 Java 8 对大量反射方法中的权限检查逻辑进行了更改,使得我们无法像之前那种利用双重反射的方式来做一些事情。这些东西我也还在学习,有兴趣的同学可以一起交流 :)

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2