使用AccessController限制权限

时间:2018-04-27 19:18:10

标签: java security

我试图阻止运行"内部"访问他们不应该做的事情的主要Java应用程序。我读过有关Policies,AccessControllers和ProtectionDomains的内容,但他们非常注重JAR。

我试过这个:

import java.nio.file.Files
import java.nio.file.Paths
import java.security.*


fun main(args: Array<String>) {
    Policy.setPolicy(object : Policy() {})
    System.setSecurityManager(SecurityManager())

    val domain = ProtectionDomain(null, Permissions() /* no permissions */)
    AccessController.doPrivileged(PrivilegedAction {
        untrusted()
    }, AccessControlContext(arrayOf(domain)))
}

fun untrusted() {
    try {
        // Works as expected
        Files.readAllBytes(Paths.get("build.gradle"))
        throw IllegalStateException("Was able to access file, but shouldn't have been able to")
    } catch (e: AccessControlException) {
    }
    try {
        // Should throw AccessControlException, but doesn't
        AccessController.doPrivileged(PrivilegedAction {
            Files.readAllBytes(Paths.get("build.gradle"))
        })
        throw IllegalStateException("Was able to access file, but shouldn't have been able to")
    } catch (e: AccessControlException) {
    }
}

即使我通过自定义有限untrusted()调用ProtectionDomain,但它似乎可以轻而易举地突破它。我希望doPrivileged中的untrusted调用与最外层ProtectionDomain(具有所有权限的主程序)和调用者&#的权限交集进行操作39; s ProtectionDomain(没有权限),导致untrusted基本上拥有0权限。

我也试过过这样的域名:

val domain = ProtectionDomain(CodeSource(URL("http://foo"), null as Array<CodeSigner>?), Permissions() /* no permissions */)

但这也不起作用 - Policy会向主程序ProtectionDomain查询,而不会调用untrusted()Policy (显然我需要更新ProtectionDomain才能正确处理&#34; http://foo&#34;但它甚至无法检查public static class SharedVariables { public static string OldText { get; set; } }

那么我的理解在哪里出错?

2 个答案:

答案 0 :(得分:2)

在对此进行一些研究之后,我想我有一个答案。我可以写一个非常更长的答案,但我想我会在这里切入追逐。

由ClassLoader加载的每个类都与ProtectionDomain + CodeSource相关联。这些有些粗糙 - 一个CodeSource表示一个类来自哪里,但它不是指向单个.class文件或任何东西的指针 - 它是指向目录或JAR。因此,同一JAR或目录中的两个类通常具有相同的权限。任何具有可识别的ProtectionDomain + CodeSource的类或脚本都可以被您的策略列入白名单/列入黑名单。

对此的异常(有点)当然是带有Permission参数的AccessController.doPrivileged。这使您可以限制代码区域的权限。但理论上, 代码可以仅使用回调调用AccessController.doPrivileged。该方法签名意味着“不检查我的整个调用堆栈的权限;只需在Policy文件中查找我的ProtectionDomain + CodeSource,看看它说的是什么。”因此,如果您运行真正不受信任的代码,最好确保一个。它有一个与您信任的应用程序不同的ProtectionDomain + CodeSource,以及b。您的策略能够识别该代码并授予其适当限制的权限。

答案 1 :(得分:0)

以下是该示例按预期运行的一种方式,即,有效地将相同域下的后续执行路径列入黑名单。基于核心权限交叉的授权模型仍应保留。必须使用-Djava.system.class.loader=com.example.Test$AppClassLoader运行该示例(仅需要此替换系统类加载器才能获得有效的单文件示例)。

强制性免责声明:虽然从技术上讲很多事情都是可能的,但是为了动态地将单个实例及其他实例列入黑名单,它们都涉及在已经非平凡的授权过程中引入某种额外的上下文。应尽可能避免采用这种方法。正如OP answer's结论所述,绝大多数情况下的适当解决方案是将可信代码与不可信代码分开打包(并且,当手动管理类到域映射时,确保将不同可信度的代码库映射到不同的域),并为结果域分配适当的权限。

package com.example;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Test {

    public static final class AppClassLoader extends URLClassLoader {

        private static final URL[] CLASS_PATH;
        private static final String SANDBOXABLE_DOMAIN_CLASS_NAME = "com.example.Test$SandboxableDomain";

        static {
            String[] paths = System.getProperty("java.class.path").split(File.pathSeparator);
            List<URL> classPath = new ArrayList<>();
            for (String path : paths) {
                try {
                    classPath.add(new URL("file://" + path));
                }
                catch (MalformedURLException ex) {}
            }
            CLASS_PATH = classPath.toArray(new URL[0]);
        }

        private final Constructor<?> sandboxableDomainCtor;

        {
            try {
                // ensure this loader defines SandboxableDomain so that normal code
                // can safely / conveniently access it via class literal
                Class<?> sandboxableDomainClass = loadClass(SANDBOXABLE_DOMAIN_CLASS_NAME, true);
                sandboxableDomainCtor = sandboxableDomainClass.getConstructor(CodeSource.class,
                        PermissionCollection.class, ClassLoader.class);
            }
            catch (ReflectiveOperationException ex) {
                throw new RuntimeException(ex);
            }
        }

        public AppClassLoader(ClassLoader parent) {
            super(CLASS_PATH, parent);
        }

        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            if (name.startsWith("java") || name.startsWith("sun")) {
                return super.loadClass(name, resolve);
            }
            Class<?> ret = findLoadedClass(name);
            if (ret != null) {
                return ret;
            }
            ProtectionDomain assignedDomain;
            byte[] classData;
            try {
                URL classResource = getResource(name.replace(".", "/") + ".class");
                CodeSource assignedCodeSource = new CodeSource(classResource, (Certificate[]) null);
                classData = Files.readAllBytes(Paths.get(classResource.toURI()));
                if (SANDBOXABLE_DOMAIN_CLASS_NAME.equals(name)) {
                    // loading the domain class itself; ensure _its own_ domain is fully privileged,
                    // so that it doesn't affect authorization
                    PermissionCollection perms = new Permissions();
                    perms.add(new AllPermission());
                    assignedDomain = new ProtectionDomain(assignedCodeSource, perms, this, null);
                }
                else {
                    // the per-class code source (URL) is unintentional; normally all classes under
                    // the same class path entry would share one
                    assignedDomain = (ProtectionDomain) sandboxableDomainCtor.newInstance(assignedCodeSource,
                            getPermissions(assignedCodeSource), this);
                }
            }
            catch (NullPointerException | URISyntaxException | IOException | ReflectiveOperationException ex) {
                throw new ClassNotFoundException(name);
            }
            ret = defineClass(name, classData, 0, classData.length, assignedDomain);
            if (resolve) {
                resolveClass(ret);
            }
            return ret;
        }

    }

    public static final class SandboxableDomain extends ProtectionDomain {

        private static final Permission DO_SANDBOXED_PERM = new RuntimePermission("com.example.doSandboxed");

        private final ThreadLocal<Boolean> sandboxed = new InheritableThreadLocal<>();

        public SandboxableDomain(CodeSource cs, PermissionCollection permissions, ClassLoader classLoader) {
            super(cs, permissions, classLoader, null);
            sandboxed.set(false);
        }

        // no equivalent doUnsandboxed here for escaping the sandbox on-demand;
        // firstly because it's fishy; secondly because it would be impossible
        // to distinguish a privileged caller based on permissions alone
        public void doSandboxed(Runnable action) {
            if (!sandboxed.get()) {
                SecurityManager sm = System.getSecurityManager();
                if (sm != null) {
                    sm.checkPermission(DO_SANDBOXED_PERM);
                }
            }
            sandboxed.set(true);
            try {
                action.run();
            }
            finally {
                sandboxed.set(false);
            }
        }

        @Override
        public boolean implies(Permission permission) {
            if (sandboxed.get()) {
                // static only (AppClassLoader only grants essentials like reading from own directory)
                PermissionCollection perms = getPermissions();
                return (perms == null) ? false : perms.implies(permission);
            }
            // static + policy
            return super.implies(permission);
        }

    }

    public static void main(String[] args) throws Exception {
        initSecurity();
        SandboxableDomain ownDomain = (SandboxableDomain) Test.class.getProtectionDomain();

        System.out.println("Try unsandboxed"); // should succeed
        untrusted();

        System.out.println("---\n\nTry sandboxed"); // should fail
        ownDomain.doSandboxed(Test::untrusted);

        System.out.println("---\n\nTry unsandboxed from within a child thread"); // should succeed
        new Thread(Test::untrusted).start();

        Thread.sleep(1000);

        System.out.println("---\n\nTry unsandboxed from within a sandboxed child thread"); // should fail
        ownDomain.doSandboxed(() -> new Thread(Test::untrusted).start());

    }

    private static void initSecurity() throws Exception {
        Path tempPolicyConfig = Files.createTempFile(null, null);
        // self-grant AllPermission
        Files.write(tempPolicyConfig,
                Collections.singletonList(new StringBuilder("grant codebase \"")
                        .append(Test.class.getProtectionDomain().getCodeSource().getLocation()).append("\"{permission ")
                        .append(AllPermission.class.getName()).append(";};").toString()));
        System.setProperty("java.security.policy", "=" + tempPolicyConfig.toString());
        System.setSecurityManager(new SecurityManager());
        Files.delete(tempPolicyConfig);
    }

    private static void untrusted() {
        try {
            untrusted0();
            System.out.println("\tSucceeded");
        }
        catch (AccessControlException ex) {
            System.out.println("\tFailed; try via doPrivileged");
            try {
                AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
                    untrusted0();
                    return null;
                });
                System.out.println("\t\tSucceeded");
            }
            catch (AccessControlException ex1) {
                System.out.println("\t\tFailed anew");
            }
        }
    }

    private static void untrusted0() {
        try {
            Files.readAllBytes(Paths.get("build.gradle"));
        }
        catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

}