因此,我有一些代码可以创建类的实例。
Class<?> c = Class.forName("MyClass");
Constructor<?> cons = c.getConstructor();
cons.setAccessible(true);
Object instance = cons.newInstance();
现在,我想对该实例设置一些限制。当我打电话时:
instance.doSomething();
我想为(实例的)那段代码设置限制。因此,从那个地方调用的方法不能做些麻烦的事情(系统调用,文件操作...)。
我试图设置一个安全管理器,但这限制了所有代码(我仍然想为其余代码读取/写入文件)。
是否可以仅限制某些对象?
最佳答案
TL; DR:Code
问题本质上是“我如何在特权低于普通权限的特定实例上调用方法?”。这里有三个要求:
代码应按实例进行授权。默认情况下,实例具有特权。
实例可能会被有选择地列入黑名单,也就是说,在实例接收到的方法调用期间,实例可能会被赋予比通常更低的特权。
黑名单必须传播到代表接收者执行的代码,特别是与接收者交互的相同类型的任何对象,包括自身。否则,例如,如果接收者依次致电
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
this.doSomethingElse();
return null;
});
doSomethingElse()
将逃离沙箱。这三个都有问题:
第一个并不是真正可以实现的,因为它假定运行时维护并公开关于线程执行栈上的实例(而不只是类)的信息,而事实并非如此。
仅当任何列入黑名单的代码没有通过
AccessController.doPrivileged(...)
声明自己的(默认的,基于类的)特权时,第二和第三种才可实现,根据设计,它随时可以选择。有其他选择吗?
好吧,你愿意走多远?修改
AccessController
/ AccessControlContext
内部?或更糟糕的是,VM内部?还是提供您自己的SecurityManager
来以满足您要求的方式从头开始重新实现上述组件的功能?如果所有人的答案都是“否”,那么我担心您的选择是有限的。顺便说一句,理想情况下,当被问到“是否可以将特定的特权(即类)赋予特定的特权?”时,您应该能够做出二进制选择,因为这将极大地简化3件事。不幸的是你不能;而且,更糟糕的是,您可能无法修改该类的实现,以致于它的所有实例(就特定的特权集而言)都可以被认为是不可信的,或者您不想简单地标记该类,并因此将其所有实例视为不受信任的对象(我相信您应该这样做!)并与之一起生活。
继续建议的解决方法。为了克服前面列出的缺点,原始问题将表述为:“如何调用具有方法接收者
ProtectionDomain
权限的特权的方法?”我将回答这个派生问题,与原始问题相反,建议:通常,代码应由其类的
ProtectionDomain
授权。默认情况下,代码是沙盒化的。在特定线程下的方法调用期间,可以选择将代码列入白名单。
白名单必须传播到接收方调用的同一类代码。
修订后的要求将通过自定义
ClassLoader
和DomainCombiner
来满足。第一个目的是为每个类分配一个不同的ProtectionDomain
5;另一个是临时替换当前AccessControlContext
中各个类的域,以实现“按需白名单”目的。 SecurityManager
被额外扩展,以防止非特权代码4创建线程。注意:我将代码重新定位到this gist,以使帖子的长度保持在限制以下。
标准免责声明:概念验证代码-需加几汤匙盐!
运行示例
按照示例策略配置文件的建议编译和部署代码,即应该有两个不相关的类路径条目(例如,文件系统级别的同级目录)-一个用于
com.example.trusted
包的类,另一个用于com.example.untrusted.Nasty
的类。还要确保已用示例一替换了策略配置,并已适当修改了其中的路径。
最后运行(当然,在适当修改类路径条目之后):
java -cp /path/to/classpath-entry-for-trusted-code:/path/to/classpath-entry-for-untrusted-code -Djava.system.class.loader=com.example.trusted.DiscriminatingClasspathClassLoader com.example.trusted.Main
希望第一次调用不受信任的方法应该成功,而第二次失败。
1特制类的实例(例如,由某个受信任的组件分配的具有其自己的域)的实例可能自己行使其自身的特权(在这种情况下不成立,因为您没有控制
instance
的类的实现,出现)。然而,这仍然不能满足第二和第三要求。2回想一下,在默认的
SecurityManager
下,当线程的Permission
imply那个权限的所有ProtectionDomain
(通常将类而不是实例映射到)时,将授予AccessControlContext
。3然后,如果您认为该类可信任,则只需要在策略级别授予权限,或者根本不授予任何权限,而不必担心每个实例在每个安全上下文中的权限,而不必担心。
4这是一个艰难的决定:如果白名单未影响相同类型的后续被调用者,则该实例将无法自行调用任何需要特权的方法。另一方面,现在它也可以与原始白名单方法接收者进行交互的任何其他相同类型的实例也成为特权!因此,您必须确保接收方不会调用任何同类的“不可信”实例。出于同样的原因,让接收者产生任何线程也是一个坏主意。
5与默认应用程序
ClassLoader
所采用的策略相反,该策略将所有驻留在单个ProtectionDomain
中相同类路径条目下的类分组。6造成不便的原因是,自定义应用程序
ProtectionDomain
的类由其父级映射到的ClassLoader
具有一个CodeSource
,它暗示所有CodeSource
引用加载程序的类路径条目下的文件。到目前为止,一切都很好。现在,当要求加载类时,我们的加载器尝试通过测试.class文件是否位于JAVA_HOME
下,来区分系统/扩展类(将其委派给其父级的加载)和应用程序类。当然,要允许这样做,它需要对JAVA_HOME
下的文件系统子树进行读取访问。不幸的是,向我们的加载器的(众所周知的)广泛的域授予相应的特权,就隐式地将特权授予位于加载器的类路径条目下的所有其他类的域,包括不可信的类。这就应该可以解释为什么为什么必须在可信和不可信代码之间进行类路径入口级隔离。当然,一如既往,有一些变通办法。例如强制对受信任的代码进行附加签名,以获取任何特权;或者可能使用更灵活的URL方案进行代码源标识和/或更改代码源隐含语义。进一步阅读:
Default Policy Implementation and Policy File Syntax
API for Privileged Blocks
Secure Coding Guidelines for Java SE - §9 - Access Control
Troubleshooting Security
历史记录:最初,此答案提出了一个几乎相同的解决方案,该解决方案滥用于JAAS的
SubjectDomainCombiner
,而不是自定义解决方案,用于动态特权修改。 “特殊” Principal
将附加到特定域,然后根据Permission
-Policy
的综合身份,由CodeSource
进行评估后,它们会产生其他Principal
。
关于java - Java设置创建实例的安全权限,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36091323/