使用java securityManager阻止我阅读文件

时间:2017-06-14 07:59:22

标签: java exception io exit-code securitymanager

在我的java代码中,我调用另一个第三方java类。

我想抓住后一个System.exit()退出代码

所以我按照post

的建议使用安全管理器

问题是我现在无法读取文件,我收到权限错误

post

如何捕获退出代码并仍然读取文件?

Published class MyClass {

class MySecurityManager extends SecurityManager {
    @Override
    public void checkExit(int status) {
        throw new SecurityException();
    }
}

public void foo() {
    MySecurityManager secManager = new MySecurityManager();
    System.setSecurityManager(secManager);

    try {
        ConfigValidator.main(new String[]{"-dirs", SdkServiceConfig.s.PROPERTIES_FILE_PATH});

        new FileInputStream(new File("/Users/eladb/WorkspaceQa/sdk-service/src/main/resources/convert_conditions.sh"));


    } catch (SecurityException e) {
        //Do something if the external code used System.exit()
        String a = "1";

    }
    catch (Exception e) {

        logger.error("failed converting properties file to proto", e);
    }
}
}

1 个答案:

答案 0 :(得分:1)

您有两个不同的问题:您的受信任代码无法读取文件,而不受信任的第三方库仍然可以不受阻碍地调用System#exit。通过授予可信代码的进一步权限,可以很容易地规避前者;后者有点难以解决。

一点背景

权限分配
代码(由ProtectionDomain封装的AccessControlContext通常以两种方式分配Permission:静态地,ClassLoader,在类定义时,和/或动态地,由Policy生效。其他不常见的替代方案也存在:例如,DomainCombiner可以修改AccessControlContext s'域(因此它们各自的代码的有效权限受到授权)在运行中,并且自定义域实现可以使用它们自己的逻辑来进行权限暗示,可能忽略或改变策略的语义。默认情况下,域的权限集是其静态动态权限的 union 。至于类如何精确地映射到域,它在很大程度上取决于加载器的实现。默认情况下,驻留在同一类路径条目下的所有类(JAR或其他类)都归入同一个域下。更严格的类加载器可以选择例如为每个类分配一个域,这可以用来防止在同一个包中的类之间进行通信。

权限评估
在默认SecurityManager下,对于特权操作(在其体内调用任何具有SecurityManager#checkXXX的方法)成功,每个域(每个方法的每个类)都有效{{1}如上所述,必须已分配正在检查的权限。但是回想一下,上下文不一定代表"真相" (实际调用堆栈) - 系统代码在早期就被优化了,而AccessControlContext调用以及可能与AccessController#doPrivileged耦合的DomainCombiner可以修改上下文的域,因此,整个授权算法。

问题和解决方法

AccessControlContext的问题在于,相应的权限(System#exit)是默认应用程序类加载器(RuntimePermission("exitVM.*") 静态分配的少数权限之一)与从类路径加载的类相关联的所有域。

我想到了许多替代方案:

  1. 安装自定义sun.misc.Launcher$AppClassLoader,根据例如尝试终止JVM进程的类来拒绝特定权限。
  2. 从"遥控器加载第三方库" location(类路径之外的目录),以便将其视为"不受信任的"代码由其类加载器构成。
  3. 创作并安装不同的应用程序类加载器,但不指定无关的权限。
  4. 将自定义域名合并器插入到访问控制上下文中,在授权决策时,所有第三方域名都替换为没有违规权限的同等域名。
  5. 为了完整起见,我应该注意,在SecurityManager级别,遗憾的是,没有任何办法可以取消静态分配的权限。

    第一个选项总体上是最方便的选项,但我不会进一步探讨它,因为:

    • 默认Policy非常灵活,这得益于它与之交互的少数组件(SecurityManager等)。一开始的背景部分旨在提醒人们这种灵活性,其中包括" quick-n' -dirty"方法覆盖倾向于瘫痪。
    • 对默认实现的粗心修改可能会导致(系统)代码以奇怪的方式行为不端。
    • 坦率地说,因为它是无聊的 - 它是一种万能的解决方案,永远提倡,而默认经理在1.2中被标准化的事实早已被遗忘。< / LI>

    第二种替代方案易于实现,但不切实际,使开发或构建过程复杂化。假设您不打算单独反思地调用库,或者通过类路径上的接口辅助它,它必须在开发期间最初存在,并在执行之前重新定位。

    第三,至少在独立的Java SE应用程序的环境中,相当简单,不应该对性能造成太大的负担。这是我赞成的方法。

    最后一个选项是最新颖和最不方便的。很难安全地实现,最大程度地降低性能,并且在每次委托给不受信任的代码之前确保组合器的存在,从而增加客户端代码的负担。

    提议的解决方案

    自定义AccessController
    以下内容将用作默认应用程序加载器的替换,或者用作上下文类加载器,或用于加载至少不受信任的类的加载器。这个实现没有什么新颖的 - 它所做的就是当假定有问题的类不是系统类时,它会阻止委派给默认的应用程序类加载器。反过来,ClassLoader不会将URLClassLoader#findClass分配给它定义的类的域。

    RuntimePermission("exitVM.*")

    如果您还希望微调分配给已加载类的域,则还必须覆盖package com.example.trusted; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.regex.Pattern; public class ClasspathClassLoader extends URLClassLoader { private static final Pattern SYSTEM_CLASS_PREFIX = Pattern.compile("((java(x)?|sun|oracle)\\.).*"); public ClasspathClassLoader(ClassLoader parent) { super(new URL[0], parent); String[] classpath = System.getProperty("java.class.path").split(File.pathSeparator); for (String classpathEntry : classpath) { try { if (!classpathEntry.endsWith(".jar") && !classpathEntry.endsWith("/")) { // URLClassLoader assumes paths without a trailing '/' to be JARs by default classpathEntry += "/"; } addURL(new URL("file:" + classpathEntry)); } catch (MalformedURLException mue) { System.err.println(MessageFormat.format("Erroneous class path entry [{0}] skipped.", classpathEntry)); } } } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> ret; synchronized (getClassLoadingLock(name)) { ret = findLoadedClass(name); if (ret != null) { return ret; } if (SYSTEM_CLASS_PREFIX.matcher(name).matches()) { return super.loadClass(name, resolve); } ret = findClass(name); if (resolve) { resolveClass(ret); } } return ret; } } 。加载器的以下变体是非常粗略的尝试。其中findClass仅为每个类路径条目创建一个域(默认情况下或多或少),但可以修改为执行不同的操作。

    constructClassDomain

    &#34;不安全&#34;代码

    package com.example.trusted;
    
    import java.io.BufferedInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.IOException;
    import java.lang.ref.WeakReference;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.nio.ByteBuffer;
    import java.security.AccessController;
    import java.security.CodeSource;
    import java.security.PrivilegedAction;
    import java.security.ProtectionDomain;
    import java.security.cert.Certificate;
    import java.text.MessageFormat;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.regex.Pattern;
    
    public final class ClasspathClassLoader extends URLClassLoader {
    
        private static final Pattern SYSTEM_CLASS_PREFIX = Pattern.compile("((java(x)?|sun|oracle)\\.).*");
        private static final List<WeakReference<ProtectionDomain>> DOMAIN_CACHE = new ArrayList<>();
    
        // constructor, loadClass same as above
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            URL classOrigin = getClassResource(name);
            if (classOrigin == null) {
                return super.findClass(name);
            }
            URL classCodeSourceOrigin = getClassCodeSourceResource(classOrigin);
            if (classCodeSourceOrigin == null) {
                return super.findClass(name);
            }
            return defineClass(name, readClassData(classOrigin), constructClassDomain(classCodeSourceOrigin));
        }
    
        private URL getClassResource(String name) {
            return AccessController.doPrivileged((PrivilegedAction<URL>) () -> getResource(name.replace(".", "/") + ".class"));
        }
    
        private URL getClassCodeSourceResource(URL classResource) {
            for (URL classpathEntry : getURLs()) {
                if (classResource.getPath().startsWith(classpathEntry.getPath())) {
                    return classpathEntry;
                }
            }
            return null;
        }
    
        private ByteBuffer readClassData(URL classResource) {
            try {
                BufferedInputStream in = new BufferedInputStream(classResource.openStream());
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int i;
                while ((i = in.read()) != -1) {
                    out.write(i);
                }
                return ByteBuffer.wrap(out.toByteArray());
            }
            catch (IOException ioe) {
                throw new RuntimeException(ioe);
            }
        }
    
        private ProtectionDomain constructClassDomain(URL classCodeSourceResource) {
            ProtectionDomain ret = getCachedDomain(classCodeSourceResource);
            if (ret == null) {
                CodeSource cs = new CodeSource(classCodeSourceResource, (Certificate[]) null);
                DOMAIN_CACHE.add(new WeakReference<>(ret = new ProtectionDomain(cs, getPermissions(cs), this, null)));
            }
            return ret;
        }
    
        private ProtectionDomain getCachedDomain(URL classCodeSourceResource) {
            for (WeakReference<ProtectionDomain> domainRef : DOMAIN_CACHE) {
                ProtectionDomain domain = domainRef.get();
                if (domain == null) {
                    DOMAIN_CACHE.remove(domainRef);
                }
                else if (domain.getCodeSource().implies(new CodeSource(classCodeSourceResource, (Certificate[]) null))) {
                    return domain;
                }
            }
            return null;
        }
    
    }
    

    切入点

    package com.example.untrusted;
    
    public class Test {
    
        public static void testExitVm() {
            System.out.println("May I...?!");
            System.exit(-1);
        }
    
    }
    

    测试

    <强>包装
    将加载器和主类放在一个JAR中(让我们称之为package com.example.trusted; import java.security.AccessControlException; import java.security.Permission; import com.example.untrusted.Test; public class Main { private static final Permission EXIT_VM_PERM = new RuntimePermission("exitVM.*"); public static void main(String... args) { System.setSecurityManager(new SecurityManager()); try { Test.testExitVm(); } catch (AccessControlException ace) { Permission deniedPerm = ace.getPermission(); if (EXIT_VM_PERM.implies(deniedPerm)) { ace.printStackTrace(); handleUnauthorizedVmExitAttempt(Integer.parseInt(deniedPerm.getName().replace("exitVM.", ""))); } } } private static void handleUnauthorizedVmExitAttempt(int exitCode) { System.out.println("here let me do it for you"); System.exit(exitCode); } } ),将演示不受信任的类放在另一个(trusted.jar)中。

    分配权限
    默认untrusted.jarPolicy)由sun.security.provider.PolicyFile处的文件以及<JRE>/lib/security/java.policypolicy.url.n属性引用的任何文件支持。修改前者(后者应默认为空),如下所示:

    <JRE>/lib/security/java.security

    请注意,在不授予安全基础结构// Standard extensions get all permissions by default grant codeBase "file:${{java.ext.dirs}}/*" { permission java.security.AllPermission; }; // no default permissions grant {}; // trusted code grant codeBase "file:///path/to/trusted.jar" { permission java.security.AllPermission; }; // third-party code grant codeBase "file:///path/to/untrusted.jar" { permission java.lang.RuntimePermission "exitVM.-1", ""; }; 的情况下,几乎不可能使扩展安全基础结构的组件(自定义类加载器,策略提供程序等)正常工作。

    <强>运行
    运行:

    AllPermission

    特权操作应该成功。

    接下来在政策文件中注明java -classpath "/path/to/trusted.jar:/path/to/untrusted.jar" -Djava.system.class.loader=com.example.trusted.ClasspathClassLoader com.example.trusted.Main 下的RuntimePermission,然后重新运行。特权操作应该失败。

    作为结束语,在调试untrusted.jar时,使用AccessControlException运行可以帮助跟踪违规域和策略配置问题。