如何限制setAccessible只有“合法”的用途?

我对java.lang.reflect.AccessibleObject.setAccessible的力量了解得越多,我就越感到惊讶。 这是从我的问题的答案( 使用reflection更改静态最终File.separatorCharunit testing )进行调整。

 import java.lang.reflect.*; public class EverythingIsTrue { static void setFinalStatic(Field field, Object newValue) throws Exception { field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); } public static void main(String args[]) throws Exception { setFinalStatic(Boolean.class.getField("FALSE"), true); System.out.format("Everything is %s", false); // "Everything is true" } } 

你可以做真正的离谱的东西:

 public class UltimateAnswerToEverything { static Integer[] ultimateAnswer() { Integer[] ret = new Integer[256]; java.util.Arrays.fill(ret, 42); return ret; } public static void main(String args[]) throws Exception { EverythingIsTrue.setFinalStatic( Class.forName("java.lang.Integer$IntegerCache") .getDeclaredField("cache"), ultimateAnswer() ); System.out.format("6 * 9 = %d", 6 * 9); // "6 * 9 = 42" } } 

据推测,APIdevise人员认识到setAccessible是多么可滥用,但必须承认它有合法的用途来提供它。 所以我的问题是:

  • 什么是setAccessible的真正合法用途?
    • Java是否被devise为没有这个需要呢?
    • 这种devise的负面后果(如果有的话)是什么?
  • 你能限制setAccessible只能用于合法用途吗?
    • 只有通过SecurityManager
      • 它是如何工作的? 白名单/黑名单,粒度等?
      • 在您的应用程序中configuration它是否很常见?
    • 无论SecurityManagerconfiguration如何,我可以将我的类写入setAccessible proof?
      • 还是我在pipe理configuration的人摆布?

我想一个更重要的问题是:我需要担心这个?

我的class级没有任何类似的强制性隐私。 单身模式(置疑其优点)现在无法执行。 正如我上面的片段所显示的那样,即使是关于Java基础工作的一些基本假设也不能保证。

这些问题是不是真的?


好吧,我刚刚证实:由于setAccessible ,Javastring不是不可变的。

 import java.lang.reflect.*; public class MutableStrings { static void mutate(String s) throws Exception { Field value = String.class.getDeclaredField("value"); value.setAccessible(true); value.set(s, s.toUpperCase().toCharArray()); } public static void main(String args[]) throws Exception { final String s = "Hello world!"; System.out.println(s); // "Hello world!" mutate(s); System.out.println(s); // "HELLO WORLD!" } } 

我是唯一一个认为这是一个巨大的关注?

我需要担心这个吗?

这完全取决于你正在写什么types的程序和什么样的架构。

如果你将一个名为foo.jar的软件组件分发给全世界的人,那么你完全可以摆脱他们的怜悯。 他们可以修改.jar中的类定义(通过逆向工程或直接字节码操作)。 他们可以在自己的JVM中运行你的代码,在这种情况下,担心会对你没有好处。

如果您正在编写一个仅通过HTTP与人员和系统进行交互的Web应用程序,并且您控制应用程序服务器,那么这也不是问题。 当然,你公司的编程人员可能会创build破坏你的单例模式的代码,但只有在他们真正想要的时候。

如果您将来的工作是在Sun Microsystems / Oracle编写代码,并且您的任务是编写Java内核或其他可信组件的代码,那么您应该注意这一点。 然而担心,只会让你失去你的头发。 无论如何,他们可能会让你阅读安全编码指南和内部文件。

如果您要编写Java小程序,则应该注意安全框架。 您会发现尝试调用setAccessible的未签名applet只会导致SecurityException。

setAccessible不是传统的完整性检查的唯一方法。 有一个叫做sun.misc.Unsafe的非API的核心Java类,它可以根据需要做任何事情,包括直接访问内存。 原生代码(JNI)也可以绕过这种控制。

在沙盒环境(例如Java Applets,JavaFX)中,每个类都有一组权限,并且可以访问不安全,setAccessible,并且定义本机实现由SecurityManager控制。

“Java访问修饰符不打算成为一个安全机制。”

这很大程度上取决于Java代码的运行位置。 核心Java类使用访问修饰符作为强制执行沙盒的安全机制。

什么是setAccessible的真正合法用途?

Java核心类使用它作为访问为了安全原因而必须保持私有的东西的简单方法。 例如,Java序列化框架在反序列化对象时使用它来调用私有对象构造函数。 有人提到System.setErr,这将是一个很好的例子,但好奇的是System类的方法setOut / setErr / setIn都使用本地代码来设置最终字段的值。

另一个明显的合理用途是需要窥视对象内部的框架(持久性,Web框架,注入)。

在我看来,debugging器不属于这个类别,因为它们通常不运行在相同的JVM进程中,而是使用其他方式(JPDA)与JVM进行接口。

Java是否被devise为没有这个需要呢?

这个问题很难回答。 我想是的,但你需要添加一些其他机制,可能不是所有的最佳。

你能限制setAccessible只能用于合法用途吗?

您可以应用的最直接的OOTB限制是有一个SecurityManager,并允许setAccessible仅来自某些来源的代码。 这就是Java已经做的事情 – 来自JAVA_HOME的标准Java类允许执行setAccessible,而来自foo.com的未签名applet类不允许执行setAccessible。 正如前面所说,这个权限是二元的,就是说有一个是否有。 没有明显的方法允许setAccessible修改某些字段/方法而禁止其他字段/方法。 然而,使用SecurityManager,你可以禁止类完全引用特定的包,有或没有reflection。

我可以编写我的类,无论SecurityManagerconfiguration如何,都可以设置为可访问吗? …或者我受制于pipe理configuration的人的摆布?

你不能,你当然是。

  • 什么是setAccessible的真正合法用途?

unit testing,JVM的内部(如实现System.setError(...) )等等。

  • Java是否被devise为没有这个需要呢?
  • 这种devise的负面后果(如果有的话)是什么?

很多事情都是不能实现的。 例如,各种Java持久性,序列化和dependency injection都依赖于reflection。 几乎所有在运行时依赖于JavaBeans约定的东西。

  • 你能限制setAccessible只能用于合法用途吗?
  • 只有通过SecurityManager

是。

  • 它是如何工作的? 白名单/黑名单,粒度等?

这取决于权限,但我相信使用setAccessible的权限是二进制的。 如果您需要粒度,则需要为要限制的类使用不同的安全pipe理器的类加载器。 我想你可以实现一个自定义的安全pipe理器,实现更细粒度的逻辑。

  • 在您的应用程序中configuration它是否很常见?

没有。

  • 无论SecurityManagerconfiguration如何,我可以将我的类写入setAccessible proof?
    • 还是我在pipe理configuration的人摆布?

不,你不能,是的,你是。

另一种select是通过源代码分析工具“执行” 例如自定义pmdfindbugs规则。 或者(比如说) grep setAccessible ...标识的代码的select性代码审查。

回应后续

我的class级没有任何类似的强制性隐私。 单身模式(置疑其优点)现在无法执行。

如果你担心,那么我想你应该担心。 但是,你真的不应该试图强迫其他程序员尊重你的devise决定。 如果他们愚蠢到无故创build多个单例(例如),他们可以忍受后果。

而如果你的意思是“隐私”来涵盖保护敏感信息免于披露的含义,那么你也在吠叫错误的树。 保护敏感数据的方法不是让不可信的代码进入处理敏感数据的安全沙箱。 Java访问修饰符不是一个安全机制。

<String example> – 我是唯一一个认为这是一个巨大的关注?

可能不是唯一一个:-)。 但海事组织,这不是一个问题。 这是可接受的事实,不可信的代码应该在沙箱中执行。 如果你有可信的代码/一个值得信赖的程序员做这样的事情,那么你的问题就会变得更糟,因为出现意外的可变string。 (想想逻辑炸弹…)

从这个angular度来看,反思确实与安全/安全是正交的。

我们如何限制反思?

Java具有安全pipe理器和ClassLoader作为其安全模型的基础。 在你的情况,我想你需要看看java.lang.reflect.ReflectPermission

但是这并不能完全解决反思的问题。 可用的reflection能力应该是一个精细的授权计划,现在不是这样。 例如,允许某些框架使用reflection(例如Hibernate),但是没有其他的代码。 或者为了debugging目的,允许程序仅以只读方式反映。

一种可能成为未来主stream的方法是使用所谓的镜像来将reflection能力与类别分离。 请参阅镜像:元级设施的devise原则 。 然而,还有其他各种解决这个问题的研究 。 但我同意dynamic语言比静态语言更严重。

我们是否应该担心反思带给我们的超级大国? 是和不是。

是的 ,这意味着Java平台应该被Classloader和安全pipe理器所保护。 混淆reflection的能力可以被视为违反。

,从某种意义上说,大多数系统无论如何都不是完全安全的。 很多课程经常被分类,你可能已经滥用了这个系统。 当然,课程可以做成final ,或者是封闭的,这样它们就不能在其他jar子里被分类。 但是根据这个,只有less数几个类是正确的(例如String)。

看到这个关于最后一堂课的答案一个很好的解释。 另见萨米Koivu的博客更多的Java黑客周围的安全。

Java的安全模型在某些方面可以被看作是不够的。 有些语言如NewSpeak采用了更为激进的模块化方法,在这种方式中,只能访问依赖倒置(默认情况下不显示)的内容。

同样重要的是要注意安全性是相对的 。 在语言级别上,您可以例如不阻止占用100%CPU的模块表单或将所有内存消耗到OutOfMemoryException 。 这种关切需要通过其他手段加以解决。 我们可能会看到未来Java扩展资源利用配额,但它不是明天:)

我可以在这方面做更多的工作,但是我想我已经明白了。