有没有办法让java中的SecurityManager有选择地授予ReflectPermission(“suppressAccessChecks”)?

有没有办法让Java中的SecurityManager有选择地授予ReflectPermission(“suppressAccessChecks”),具体取决于调用setAccessible()的详细信息? 我认为没有办法做到这一点。

对于某些沙盒代码,它将非常有用(例如运行各种动态JVM语言)以允许调用setAccessible()reflectionAPI,但当在发起的类的方法/字段上调用setAccessible()时在沙盒代码中。

除非选择性授予ReflectPermission(“suppressAccessChecks”),否则是否有任何其他建议? 在所有情况下,如果SecurityManager.checkMemberAccess()具有足够的限制性,可能会安全吗?

也许查看调用堆栈就足够了吗? 就像是:

import java.lang.reflect.ReflectPermission; import java.security.Permission; public class Test { private static int foo; public static void main(String[] args) throws Exception { System.setSecurityManager(new SecurityManager() { @Override public void checkPermission(Permission perm) { if (perm instanceof ReflectPermission && "suppressAccessChecks".equals(perm.getName())) { for (StackTraceElement elem : Thread.currentThread().getStackTrace()) { if ("Test".equals(elem.getClassName()) && "badSetAccessible".equals(elem.getMethodName())) { throw new SecurityException(); } } } } }); goodSetAccessible(); // works badSetAccessible(); // throws SecurityException } private static void goodSetAccessible() throws Exception { Test.class.getDeclaredField("foo").setAccessible(true); } private static void badSetAccessible() throws Exception { Test.class.getDeclaredField("foo").setAccessible(true); } } 

这可以使用字节码编织与像Byte Buddy这样的库。 您可以创建自定义权限,而不是使用标准的ReflectPermission("suppressAccessChecks")权限,并使用自定义方法替换AccessibleObject.setAccessible方法,这些方法使用Byte Buddy转换检查您的自定义权限。

此自定义权限工作的一种可能方式是使其基于访问者的类加载器和正在修改访问的对象进行访问。 使用它允许隔离代码(从与其自己的类加载器分开加载的代码)在其自己的jar中的类上调用setAccessible ,而不是标准Java类或您自己的应用程序类。

这样的权限可能如下所示:

 public class UserSetAccessiblePermission extends Permission { private final ClassLoader loader; public UserSetAccessiblePermission(ClassLoader loader) { super("userSetAccessible"); this.loader = loader; } @Override public boolean implies(Permission permission) { if (!(permission instanceof UserSetAccessiblePermission)) { return false; } UserSetAccessiblePermission that = (UserSetAccessiblePermission) permission; return that.loader == this.loader; } // equals and hashCode omitted @Override public String getActions() { return ""; } } 

这就是我选择实现此权限的方式,但它可以是包或类白名单或黑名单。

现在有了这个权限,您可以创建一个存根类来替换AccessibleObject.setAcessible方法,而不是使用此权限。

 public class AccessibleObjectStub { private final static Permission STANDARD_ACCESS_PERMISSION = new ReflectPermission("suppressAccessChecks"); public static void setAccessible(@This AccessibleObject ao, boolean flag) throws SecurityException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { Permission permission = STANDARD_ACCESS_PERMISSION; if (isFromUserLoader(ao)) { try { permission = getUserAccessPermission(ao); } catch (Exception e) { // Ignore. Use standard permission. } } sm.checkPermission(permission); } } private static Permission getUserAccessPermission(AccessibleObject ao) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, ClassNotFoundException { ClassLoader aoClassLoader = getAccessibleObjectLoader(ao); return new UserSetAccessiblePermission(aoClassLoader); } private static ClassLoader getAccessibleObjectLoader(AccessibleObject ao) { return AccessController.doPrivileged(new PrivilegedAction() { @Override public ClassLoader run() { if (ao instanceof Executable) { return ((Executable) ao).getDeclaringClass().getClassLoader(); } else if (ao instanceof Field) { return ((Field) ao).getDeclaringClass().getClassLoader(); } throw new IllegalStateException("Unknown AccessibleObject type: " + ao.getClass()); } }); } private static boolean isFromUserLoader(AccessibleObject ao) { ClassLoader loader = getAccessibleObjectLoader(ao); if (loader == null) { return false; } // Check that the class loader instance is of a custom type return UserClassLoaders.isUserClassLoader(loader); } } 

有了这两个类,您现在可以使用Byte Buddy构建一个转换器,用于转换Java AccessibleObject以使用您的存根。

创建转换器的第一步是创建一个Byte Buddy类型池,其中包含引导类和包含存根的jar文件。

 final TypePool bootstrapTypePool = TypePool.Default.of( new ClassFileLocator.Compound( new ClassFileLocator.ForJarFile(jarFile), ClassFileLocator.ForClassLoader.of(null))); 

接下来使用reflection来获取对AccessObject.setAccessible0方法的引用。 这是一个私有方法,如果对setAccessible的调用通过权限检查,则实际修改可访问性。

 Method setAccessible0Method; try { String setAccessible0MethodName = "setAccessible0"; Class[] paramTypes = new Class[2]; paramTypes[0] = AccessibleObject.class; paramTypes[1] = boolean.class; setAccessible0Method = AccessibleObject.class .getDeclaredMethod(setAccessible0MethodName, paramTypes); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } 

通过这两个部件,可以构建变压器。

 AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() { @Override public DynamicType.Builder transform( DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader) { return builder.method( ElementMatchers.named("setAccessible") .and(ElementMatchers.takesArguments(boolean.class))) .intercept(MethodDelegation.to( bootstrapTypePool.describe( "com.leacox.sandbox.security.stub.java.lang.reflect.AccessibleObjectStub") .resolve()) .andThen(MethodCall.invoke(setAccessible0Method).withThis().withAllArguments())); } } 

最后一步是安装Byte Buddy Java代理并执行转换。 包含存根的jar也必须附加到引导类路径。 这是必要的,因为AccessibleObject类将由引导加载程序加载,因此任何存根也必须在那里加载。

 Instrumentation instrumentation = ByteBuddyAgent.install(); // Append the jar containing the stub replacement to the bootstrap classpath instrumentation.appendToBootstrapClassLoaderSearch(jarFile); AgentBuilder agentBuilder = new AgentBuilder.Default() .disableClassFormatChanges() .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .ignore(none()); // disable default ignores so we can transform Java classes .type(ElementMatchers.named("java.lang.reflect.AccessibleObject")) .transform(transformer) .installOnByteBuddyAgent(); 

这在使用SecurityManager并将存根类和应用选择权限的代码隔离在运行时加载的单独jar中时将起作用。 必须在运行时加载jar而不是将它们作为标准依赖项或捆绑的库复杂化,但这似乎是在使用SecurityManager时隔离不受信任的代码的要求。

我的Github repo sandbox-runtime有一个完整的,深入的沙盒运行时环境示例,它执行隔离的不受信任的代码和更具选择性的reflection权限。 我还有一篇博文,其中详细介绍了有选择的setAccessible权限部分。

FWI:由于setAccessible似乎只有一个有效的用例序列化,我认为你可能经常逃脱完全否定它。

也就是说,我对如何做这类事情感兴趣,因为我也必须编写一个安全管理器来阻止动态加载的代码执行我们的应用程序容器代码需要做的事情。