【问题标题】:How to check caller class origin in SecurityManager?如何在 SecurityManager 中检查调用者类的来源?
【发布时间】:2019-12-25 23:42:39
【问题描述】:

我有一个用于受信任应用程序代码的 ClassLoader 和一个用于用户提交(不受信任)代码的单独 ClassLoader。

我希望安全管理器限制用户提交的代码。如何从 SecurityManager 中检查调用方来源?查看伪代码:

System.setSecurityManager(new SecurityManager() {
    public void checkPermission(Permission permission) {
        if (/*caller class is not loaded by the trusted classloader*/) {
            throw new SecurityException("You do not have permissions.");
        }
    }
});

我已经尝试过的:

  • StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass().getClassLoader() 首先检查权限,因此会给出堆栈溢出异常。

  • Thread.currentThread().getStackTrace()[2].getClassLoaderName() 是不安全的,因为它只提供类加载器名称而不是类对象,如果不受信任的加载器的规范名称与受信任的加载器相同,那么这是一个安全问题。

【问题讨论】:

  • 首先检查权限,因此它会给出堆栈溢出异常。 - 这不能通过特殊情况下的权限检查来解决,因此它不会导致递归?
  • @the8472 你能解释一下你想如何“特例”吗?因为您无法真正确定哪个类正在请求使用 StackWalker 的权限。可能是安全管理器,也可能是恶意代码。
  • 好吧,您只需尽早获取 stackwalker,以确保不存在恶意代码,因此您可以在启动阶段确定该特定调用,而无需进一步检查。安全检查发生在getInstance() 期间,因此您只需授予一次。
  • 用户提交的代码应该在自己的ProtectionDomain/CodeSource中,因此有自己的权限(与您自己的代码分开)。然后你应该可以让SecurityManager 完成它的工作。

标签: java java-security java-security-manager


【解决方案1】:

我找到了一个临时解决方案,但并不完美:

System.setSecurityManager(new SecurityManager() {
    public void checkPermission(Permission permission) {
        Class<?> caller = SecurityManager.class;
        Class<?>[] classContext = this.getClassContext();
        int loopAmount = 0;

        while (caller.getCanonicalName() == null
                 || !caller.getCanonicalName().startsWith("nl")) {
            caller = classContext[loopAmount];
            loopAmount++;
        }

        if (caller.getClassLoader() != trustedClassLoader) {
            throw new SecurityException("You do not have permissions.");
        }
    }
});

所有不受信任的类的规范名称都必须以“nl”开头。这是从类上下文中获取调用者类的唯一方法,因为调用者类的数组位置是未知的,并且上下文数组的最后一个元素始终是具有 main 方法的类。

【讨论】:

    【解决方案2】:

    首先,SecurityManager 有一个受保护的方法getClassContext()
    您的代码如下所示:

    System.setSecurityManager(new SecurityManager() {
        public void checkPermission(Permission permission) {
            Class<?> caller = getClassContext()[1];
            ClassLoader ccl = caller.getClassLoader();
            if (ccl != null || ccl != getClass().getClassLoader()) {
                throw new SecurityException("You do not have permissions.");
            }
        }
    });
    

    其次,如果要使用StackWalker,建议你复用StackWalker实例:

    StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
    System.setSecurityManager(new SecurityManager() {
        public void checkPermission(Permission permission) {
            Class<?> caller = walker.getCallerClass();
            ClassLoader ccl = caller.getClassLoader();
            if (ccl != null || ccl != getClass().getClassLoader()) {
                throw new SecurityException("You do not have permissions.");
            }
        }
    });
    

    第三,这很可能不会达到你想要的效果。安全检查在整个 JDK 中完成,因此调用者可能在任意数量的堆栈级别之外,需要您检查整个堆栈(提示:在您第二次访问堆栈中的 SecurityManager 时中断)。


    相反,定义一个策略(创建一个 java 策略文件),在其中授予代码所有权限并使用 java.lang.SecurityManager。

    如果无法编写自己的策略文件,也可以使用Policy.setPolicy() 安装自己的java.security.Policy 实现。

    实现java.security.Policy的一些提示:

    • 覆盖impliesgetPermissions 方法。说真的。
    • 在 Policy 类中找到您自己的 ProtectionDomain。 (private static final ProtectionDomain MY_PD = MyPolicy.class.getProtectionDomain())
    • 如果检查是针对您自己的 ProtectionDomain,请使用快速路径。在这种情况下不要调用其他代码,否则可能会导致 StackOverflow。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-21
      • 1970-01-01
      • 2018-01-05
      相关资源
      最近更新 更多