在我的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);
}
}
}
答案 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.*")
静态分配的少数权限之一)与从类路径加载的类相关联的所有域。
我想到了许多替代方案:
sun.misc.Launcher$AppClassLoader
,根据例如尝试终止JVM进程的类来拒绝特定权限。为了完整起见,我应该注意,在SecurityManager
级别,遗憾的是,没有任何办法可以取消静态分配的权限。
第一个选项总体上是最方便的选项,但我不会进一步探讨它,因为:
Policy
非常灵活,这得益于它与之交互的少数组件(SecurityManager
等)。一开始的背景部分旨在提醒人们这种灵活性,其中包括" quick-n' -dirty"方法覆盖倾向于瘫痪。第二种替代方案易于实现,但不切实际,使开发或构建过程复杂化。假设您不打算单独反思地调用库,或者通过类路径上的接口辅助它,它必须在开发期间最初存在,并在执行之前重新定位。
第三,至少在独立的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.jar
(Policy
)由sun.security.provider.PolicyFile
处的文件以及<JRE>/lib/security/java.policy
中policy.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
运行可以帮助跟踪违规域和策略配置问题。